mirror of https://github.com/labapart/gattlib
Created initial gattlib support
parent
cc27526fdc
commit
fc58fc0307
|
@ -22,10 +22,13 @@
|
|||
|
||||
cmake_minimum_required(VERSION 2.6)
|
||||
|
||||
project(gattlib)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
|
||||
include_directories(bluez/attrib bluez/btio bluez/src)
|
||||
include_directories(include bluez/attrib bluez/btio bluez/src)
|
||||
|
||||
# Bluez specific files
|
||||
set(gattlib_SRCS bluez/attrib/att.c
|
||||
bluez/attrib/gatt.c
|
||||
bluez/attrib/gattrib.c
|
||||
|
@ -38,14 +41,48 @@ set(gattlib_SRCS bluez/attrib/att.c
|
|||
|
||||
add_definitions(-DVERSION="4.101")
|
||||
|
||||
# Gattlib files
|
||||
list(APPEND gattlib_SRCS src/gattlib_connect.c
|
||||
src/gattlib_discover.c
|
||||
src/gattlib_read_write.c)
|
||||
|
||||
# Added Glib support
|
||||
pkg_search_module(GLIB REQUIRED glib-2.0)
|
||||
include_directories(${GLIB_INCLUDE_DIRS})
|
||||
list(APPEND gattlib_LIBS ${GLIB_LIBRARIES})
|
||||
|
||||
# gattlib
|
||||
add_library(gattlib ${gattlib_SRCS})
|
||||
add_library(gattlib SHARED ${gattlib_SRCS})
|
||||
target_link_libraries(gattlib ${gattlib_LIBS})
|
||||
|
||||
# Examples
|
||||
add_subdirectory(examples/gatttool)
|
||||
|
||||
#
|
||||
# Packaging
|
||||
#
|
||||
set(CPACK_PACKAGE_INSTALL_DIRECTORY /usr CACHE STRING "Install directory (default: /usr).")
|
||||
set(CPACK_PACKAGE_VERSION 0.1)
|
||||
set(CPACK_PACKAGE_CONTACT "Olivier Martin <olivier@labapart.com>")
|
||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Library to access GATT information from a Bluetooth device.")
|
||||
set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}-${CPACK_PACKAGE_VERSION}")
|
||||
|
||||
#
|
||||
# Debian package
|
||||
#
|
||||
set(CPACK_GENERATOR "DEB;RPM;ZIP")
|
||||
|
||||
# Detect platform architecture to use it for the Debian package
|
||||
execute_process(COMMAND dpkg --print-architecture OUTPUT_VARIABLE CPACK_DEBIAN_PACKAGE_ARCHITECTURE OUTPUT_QUIET)
|
||||
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libglib2.0-0")
|
||||
|
||||
#
|
||||
# List of file to install
|
||||
#
|
||||
configure_file(gattlib.pc.in gattlib.pc @ONLY)
|
||||
install(TARGETS gattlib LIBRARY DESTINATION lib)
|
||||
install(FILES include/gattlib.h DESTINATION include)
|
||||
install(FILES ${PROJECT_BINARY_DIR}/gattlib.pc DESTINATION lib/pkgconfig)
|
||||
|
||||
include(CPack)
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include "att.h"
|
||||
#include "btio.h"
|
||||
#include "gattrib.h"
|
||||
#include "gattlib_internal.h"
|
||||
|
||||
#define GATT_TIMEOUT 30
|
||||
|
||||
|
@ -43,9 +44,9 @@ struct _GAttrib {
|
|||
gint refs;
|
||||
uint8_t *buf;
|
||||
int buflen;
|
||||
guint read_watch;
|
||||
guint write_watch;
|
||||
guint timeout_watch;
|
||||
GSource* read_watch;
|
||||
GSource* write_watch;
|
||||
GSource* timeout_watch;
|
||||
GQueue *requests;
|
||||
GQueue *responses;
|
||||
GSList *events;
|
||||
|
@ -193,13 +194,13 @@ static void attrib_destroy(GAttrib *attrib)
|
|||
attrib->events = NULL;
|
||||
|
||||
if (attrib->timeout_watch > 0)
|
||||
g_source_remove(attrib->timeout_watch);
|
||||
g_source_destroy(attrib->timeout_watch);
|
||||
|
||||
if (attrib->write_watch > 0)
|
||||
g_source_remove(attrib->write_watch);
|
||||
g_source_destroy(attrib->write_watch);
|
||||
|
||||
if (attrib->read_watch > 0)
|
||||
g_source_remove(attrib->read_watch);
|
||||
g_source_destroy(attrib->read_watch);
|
||||
|
||||
if (attrib->io)
|
||||
g_io_channel_unref(attrib->io);
|
||||
|
@ -325,9 +326,9 @@ static gboolean can_write_data(GIOChannel *io, GIOCondition cond,
|
|||
|
||||
cmd->sent = TRUE;
|
||||
|
||||
if (attrib->timeout_watch == 0)
|
||||
attrib->timeout_watch = g_timeout_add_seconds(GATT_TIMEOUT,
|
||||
disconnect_timeout, attrib);
|
||||
if (attrib->timeout_watch == 0) {
|
||||
attrib->timeout_watch = gattlib_timeout_add_seconds(GATT_TIMEOUT, disconnect_timeout, attrib);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
@ -346,8 +347,10 @@ static void wake_up_sender(struct _GAttrib *attrib)
|
|||
return;
|
||||
|
||||
attrib = g_attrib_ref(attrib);
|
||||
attrib->write_watch = g_io_add_watch_full(attrib->io,
|
||||
G_PRIORITY_DEFAULT, G_IO_OUT,
|
||||
// Tell the main loop to write the command with `can_write_data` when
|
||||
// write can be done without blocking (G_IO_OUT).
|
||||
attrib->write_watch = gattlib_watch_connection_full(attrib->io,
|
||||
G_IO_OUT,
|
||||
can_write_data, attrib, destroy_sender);
|
||||
}
|
||||
|
||||
|
@ -392,7 +395,7 @@ static gboolean received_data(GIOChannel *io, GIOCondition cond, gpointer data)
|
|||
return TRUE;
|
||||
|
||||
if (attrib->timeout_watch > 0) {
|
||||
g_source_remove(attrib->timeout_watch);
|
||||
g_source_destroy(attrib->timeout_watch);
|
||||
attrib->timeout_watch = 0;
|
||||
}
|
||||
|
||||
|
@ -468,9 +471,9 @@ GAttrib *g_attrib_new(GIOChannel *io)
|
|||
attrib->requests = g_queue_new();
|
||||
attrib->responses = g_queue_new();
|
||||
|
||||
attrib->read_watch = g_io_add_watch(attrib->io,
|
||||
attrib->read_watch = gattlib_watch_connection_full(attrib->io,
|
||||
G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
|
||||
received_data, attrib);
|
||||
received_data, attrib, NULL);
|
||||
|
||||
return g_attrib_ref(attrib);
|
||||
}
|
||||
|
|
|
@ -31,11 +31,12 @@ extern "C" {
|
|||
#define GATTRIB_ALL_EVENTS 0xFF
|
||||
#define GATTRIB_ALL_REQS 0xFE
|
||||
|
||||
#include "gattlib.h"
|
||||
|
||||
struct _GAttrib;
|
||||
typedef struct _GAttrib GAttrib;
|
||||
|
||||
typedef void (*GAttribResultFunc) (guint8 status, const guint8 *pdu,
|
||||
guint16 len, gpointer user_data);
|
||||
guint16 len, gpointer user_data);
|
||||
typedef void (*GAttribDisconnectFunc)(gpointer user_data);
|
||||
typedef void (*GAttribDebugFunc)(const char *str, gpointer user_data);
|
||||
typedef void (*GAttribNotifyFunc)(const guint8 *pdu, guint16 len,
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
#include <glib.h>
|
||||
|
||||
#include "btio.h"
|
||||
#include "gattlib_internal.h"
|
||||
|
||||
#ifndef BT_FLUSHABLE
|
||||
#define BT_FLUSHABLE 8
|
||||
|
@ -70,6 +71,7 @@ struct connect {
|
|||
BtIOConnect connect;
|
||||
gpointer user_data;
|
||||
GDestroyNotify destroy;
|
||||
GSource* source;
|
||||
};
|
||||
|
||||
struct accept {
|
||||
|
@ -94,8 +96,10 @@ static void server_remove(struct server *server)
|
|||
|
||||
static void connect_remove(struct connect *conn)
|
||||
{
|
||||
if (conn->destroy)
|
||||
g_source_destroy(conn->source);
|
||||
if (conn->destroy) {
|
||||
conn->destroy(conn->user_data);
|
||||
}
|
||||
g_free(conn);
|
||||
}
|
||||
|
||||
|
@ -238,7 +242,7 @@ static void connect_add(GIOChannel *io, BtIOConnect connect,
|
|||
conn->destroy = destroy;
|
||||
|
||||
cond = G_IO_OUT | G_IO_ERR | G_IO_HUP | G_IO_NVAL;
|
||||
g_io_add_watch_full(io, G_PRIORITY_DEFAULT, cond, connect_cb, conn,
|
||||
conn->source = gattlib_watch_connection_full(io, cond, connect_cb, conn,
|
||||
(GDestroyNotify) connect_remove);
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#define BT_IO_H
|
||||
|
||||
#include <glib.h>
|
||||
#include "gattlib.h"
|
||||
|
||||
typedef enum {
|
||||
BT_IO_ERROR_DISCONNECTED,
|
||||
|
@ -71,13 +72,6 @@ typedef enum {
|
|||
BT_IO_OPT_PRIORITY,
|
||||
} BtIOOption;
|
||||
|
||||
typedef enum {
|
||||
BT_IO_SEC_SDP = 0,
|
||||
BT_IO_SEC_LOW,
|
||||
BT_IO_SEC_MEDIUM,
|
||||
BT_IO_SEC_HIGH,
|
||||
} BtIOSecLevel;
|
||||
|
||||
typedef enum {
|
||||
BT_IO_MODE_BASIC = 0,
|
||||
BT_IO_MODE_RETRANS,
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
*
|
||||
* GattLib - GATT Library
|
||||
*
|
||||
* Copyright (C) 2016 Olivier Martin <olivier@labapart.org>
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __GATTLIB_H__
|
||||
#define __GATTLIB_H__
|
||||
|
||||
#include <glib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <bluetooth/bluetooth.h>
|
||||
#include <bluetooth/sdp.h>
|
||||
#include <bluetooth/uuid.h>
|
||||
|
||||
typedef enum {
|
||||
BT_IO_SEC_SDP = 0,
|
||||
BT_IO_SEC_LOW,
|
||||
BT_IO_SEC_MEDIUM,
|
||||
BT_IO_SEC_HIGH,
|
||||
} BtIOSecLevel;
|
||||
|
||||
typedef struct _GAttrib GAttrib;
|
||||
|
||||
typedef struct _gatt_connection_t {
|
||||
GIOChannel *io;
|
||||
|
||||
GAttrib *attrib;
|
||||
} gatt_connection_t;
|
||||
|
||||
typedef void (*gatt_connect_cb_t)(gatt_connection_t* connection);
|
||||
typedef void (*gatt_cb_t) (GSList *l, guint8 status, gpointer user_data);
|
||||
typedef void* (*gatt_read_cb_t)(void* buffer, size_t buffer_len);
|
||||
|
||||
/**
|
||||
* @param src Local Adaptater interface
|
||||
* @param dst Remote Bluetooth address
|
||||
* @param dst_type Set LE address type (either BDADDR_LE_PUBLIC or BDADDR_LE_RANDOM)
|
||||
* @param sec_level Set security level (either BT_IO_SEC_LOW, BT_IO_SEC_MEDIUM, BT_IO_SEC_HIGH)
|
||||
* @param psm Specify the PSM for GATT/ATT over BR/EDR
|
||||
* @param mtu Specify the MTU size
|
||||
*/
|
||||
gatt_connection_t *gattlib_connect(const gchar *src, const gchar *dst,
|
||||
uint8_t dest_type, BtIOSecLevel sec_level, int psm, int mtu);
|
||||
|
||||
gatt_connection_t *gattlib_connect_async(const gchar *src, const gchar *dst,
|
||||
uint8_t dest_type, BtIOSecLevel sec_level, int psm, int mtu,
|
||||
gatt_connect_cb_t connect_cb);
|
||||
|
||||
int gattlib_disconnect(gatt_connection_t* connection);
|
||||
|
||||
typedef struct {
|
||||
uint16_t attr_handle_start;
|
||||
uint16_t attr_handle_end;
|
||||
bt_uuid_t uuid;
|
||||
} gattlib_primary_service_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t properties;
|
||||
uint16_t value_handle;
|
||||
bt_uuid_t uuid;
|
||||
} gattlib_characteristic_t;
|
||||
|
||||
int gattlib_discover_primary(gatt_connection_t* connection, gattlib_primary_service_t** services, int* services_count);
|
||||
int gattlib_discover_char(gatt_connection_t* connection, gattlib_characteristic_t** characteristics, int* characteristic_count);
|
||||
|
||||
int gattlib_read_char_by_uuid(gatt_connection_t* connection, bt_uuid_t* uuid, void* buffer, size_t buffer_len);
|
||||
int gattlib_read_char_by_uuid_async(gatt_connection_t* connection, bt_uuid_t* uuid, gatt_read_cb_t gatt_read_cb);
|
||||
|
||||
int gattlib_write_char_by_handle(gatt_connection_t* connection, uint16_t handle, void* buffer, size_t buffer_len);
|
||||
|
||||
typedef void (*gattlib_event_handler_t)(uint16_t handle, const uint8_t* data, size_t data_length);
|
||||
void gattlib_register_notification(gattlib_event_handler_t notification_handler);
|
||||
void gattlib_register_indication(gattlib_event_handler_t indication_handler);
|
||||
|
||||
int gattlib_uuid_to_string(const bt_uuid_t *uuid, char *str, size_t n);
|
||||
int gattlib_string_to_uuid(bt_uuid_t *uuid, const char *str);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
*
|
||||
* GattLib - GATT Library
|
||||
*
|
||||
* Copyright (C) 2016 Olivier Martin <olivier@labapart.org>
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __GATTLIB_INTERNAL_H__
|
||||
#define __GATTLIB_INTERNAL_H__
|
||||
|
||||
#include "gattlib.h"
|
||||
|
||||
struct gattlib_thread_t {
|
||||
int ref;
|
||||
pthread_t thread;
|
||||
GMainContext* loop_context;
|
||||
GMainLoop* loop;
|
||||
};
|
||||
|
||||
extern struct gattlib_thread_t g_gattlib_thread;
|
||||
|
||||
/**
|
||||
* Watch the GATT connection for conditions
|
||||
*/
|
||||
GSource* gattlib_watch_connection_full(GIOChannel* io, GIOCondition condition,
|
||||
GIOFunc func, gpointer user_data, GDestroyNotify notify);
|
||||
GSource* gattlib_timeout_add_seconds(guint interval, GSourceFunc function, gpointer data);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,343 @@
|
|||
/*
|
||||
*
|
||||
* GattLib - GATT Library
|
||||
*
|
||||
* Copyright (C) 2016 Olivier Martin <olivier@labapart.org>
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <bluetooth/bluetooth.h>
|
||||
#include <bluetooth/uuid.h>
|
||||
|
||||
#include "gattlib_internal.h"
|
||||
|
||||
#include "att.h"
|
||||
#include "btio.h"
|
||||
#include "gattrib.h"
|
||||
|
||||
struct gattlib_thread_t g_gattlib_thread = { 0 };
|
||||
|
||||
static gattlib_event_handler_t g_notification_handler;
|
||||
static gattlib_event_handler_t g_indication_handler;
|
||||
|
||||
typedef struct {
|
||||
gatt_connection_t* conn;
|
||||
gatt_connect_cb_t connect_cb;
|
||||
int connected;
|
||||
GError* error;
|
||||
} io_connect_arg_t;
|
||||
|
||||
static void events_handler(const uint8_t *pdu, uint16_t len, gpointer user_data)
|
||||
{
|
||||
GAttrib *attrib = user_data;
|
||||
uint8_t opdu[ATT_MAX_MTU];
|
||||
uint16_t handle, i, olen = 0;
|
||||
|
||||
handle = att_get_u16(&pdu[1]);
|
||||
|
||||
switch (pdu[0]) {
|
||||
case ATT_OP_HANDLE_NOTIFY:
|
||||
if (g_notification_handler) {
|
||||
g_notification_handler(handle, &pdu[3], len);
|
||||
}
|
||||
break;
|
||||
case ATT_OP_HANDLE_IND:
|
||||
if (g_indication_handler) {
|
||||
g_indication_handler(handle, &pdu[3], len);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
g_print("Invalid opcode\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (pdu[0] == ATT_OP_HANDLE_NOTIFY)
|
||||
return;
|
||||
|
||||
olen = enc_confirmation(opdu, sizeof(opdu));
|
||||
|
||||
if (olen > 0)
|
||||
g_attrib_send(attrib, 0, opdu[0], opdu, olen, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
static gboolean io_listen_cb(gpointer user_data) {
|
||||
gatt_connection_t *conn = user_data;
|
||||
|
||||
g_attrib_register(conn->attrib, ATT_OP_HANDLE_NOTIFY, events_handler, conn->attrib, NULL);
|
||||
g_attrib_register(conn->attrib, ATT_OP_HANDLE_IND, events_handler, conn->attrib, NULL);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void io_connect_cb(GIOChannel *io, GError *err, gpointer user_data) {
|
||||
io_connect_arg_t* io_connect_arg = user_data;
|
||||
|
||||
if (err) {
|
||||
io_connect_arg->error = err;
|
||||
|
||||
// Call callback if defined
|
||||
if (io_connect_arg->connect_cb) {
|
||||
io_connect_arg->connect_cb(NULL);
|
||||
}
|
||||
} else {
|
||||
io_connect_arg->conn->attrib = g_attrib_new(io);
|
||||
|
||||
//
|
||||
// Register the listener callback
|
||||
//
|
||||
GSource *source = g_idle_source_new ();
|
||||
assert(source != NULL);
|
||||
|
||||
g_source_set_callback(source, io_listen_cb, io_connect_arg->conn, NULL);
|
||||
|
||||
// Attaches the listener to the main loop context
|
||||
guint id = g_source_attach(source, g_gattlib_thread.loop_context);
|
||||
g_source_unref (source);
|
||||
assert(id != 0);
|
||||
|
||||
//
|
||||
// Call callback if defined
|
||||
//
|
||||
if (io_connect_arg->connect_cb) {
|
||||
io_connect_arg->connect_cb(io_connect_arg->conn);
|
||||
}
|
||||
}
|
||||
if (io_connect_arg->connect_cb) {
|
||||
free(io_connect_arg);
|
||||
} else {
|
||||
io_connect_arg->connected = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
static void *connection_thread(void* arg) {
|
||||
struct gattlib_thread_t* loop_thread = arg;
|
||||
|
||||
loop_thread->loop_context = g_main_context_new();
|
||||
loop_thread->loop = g_main_loop_new(loop_thread->loop_context, TRUE);
|
||||
|
||||
g_main_loop_run(loop_thread->loop);
|
||||
g_main_loop_unref(loop_thread->loop);
|
||||
assert(0);
|
||||
}
|
||||
|
||||
static gatt_connection_t *initialize_gattlib_connection(const gchar *src, const gchar *dst,
|
||||
uint8_t dest_type, BtIOSecLevel sec_level, int psm, int mtu,
|
||||
gatt_connect_cb_t connect_cb,
|
||||
io_connect_arg_t* io_connect_arg)
|
||||
{
|
||||
bdaddr_t sba, dba;
|
||||
GError *err = NULL;
|
||||
|
||||
/* Check if the GattLib thread has been started */
|
||||
if (g_gattlib_thread.ref == 0) {
|
||||
/* Start it */
|
||||
|
||||
/* Create a thread that will handle Bluetooth events */
|
||||
int error = pthread_create(&g_gattlib_thread.thread, NULL, &connection_thread, &g_gattlib_thread);
|
||||
if (error != 0) {
|
||||
fprintf(stderr, "Cannot create connection thread: %s", strerror(error));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Wait for the loop to be started */
|
||||
while (!g_gattlib_thread.loop || !g_main_loop_is_running (g_gattlib_thread.loop)) {
|
||||
usleep(1000);
|
||||
}
|
||||
} else {
|
||||
/* Increase the reference to know how many GATT connection use the loop */
|
||||
g_gattlib_thread.ref++;
|
||||
}
|
||||
|
||||
/* Remote device */
|
||||
if (dst == NULL) {
|
||||
fprintf(stderr, "Remote Bluetooth address required\n");
|
||||
return NULL;
|
||||
}
|
||||
str2ba(dst, &dba);
|
||||
|
||||
/* Local adapter */
|
||||
if (src != NULL) {
|
||||
if (!strncmp(src, "hci", 3))
|
||||
hci_devba(atoi(src + 3), &sba);
|
||||
else
|
||||
str2ba(src, &sba);
|
||||
} else
|
||||
bacpy(&sba, BDADDR_ANY);
|
||||
|
||||
/* Not used for BR/EDR */
|
||||
if ((dest_type != BDADDR_LE_PUBLIC) && (dest_type != BDADDR_LE_RANDOM)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ((sec_level != BT_IO_SEC_LOW) && (sec_level != BT_IO_SEC_MEDIUM) && (sec_level != BT_IO_SEC_HIGH)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
gatt_connection_t* conn = malloc(sizeof(gatt_connection_t));
|
||||
if (conn == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Intialize bt_io_connect argument */
|
||||
io_connect_arg->conn = conn;
|
||||
io_connect_arg->connect_cb = connect_cb;
|
||||
io_connect_arg->connected = FALSE;
|
||||
io_connect_arg->error = NULL;
|
||||
|
||||
if (psm == 0)
|
||||
conn->io = bt_io_connect(BT_IO_L2CAP, io_connect_cb, io_connect_arg, NULL, &err,
|
||||
BT_IO_OPT_SOURCE_BDADDR, &sba,
|
||||
BT_IO_OPT_DEST_BDADDR, &dba,
|
||||
BT_IO_OPT_DEST_TYPE, dest_type,
|
||||
BT_IO_OPT_CID, ATT_CID,
|
||||
BT_IO_OPT_SEC_LEVEL, sec_level,
|
||||
BT_IO_OPT_INVALID);
|
||||
else
|
||||
conn->io = bt_io_connect(BT_IO_L2CAP, io_connect_cb, io_connect_arg, NULL, &err,
|
||||
BT_IO_OPT_SOURCE_BDADDR, &sba,
|
||||
BT_IO_OPT_DEST_BDADDR, &dba,
|
||||
BT_IO_OPT_PSM, psm,
|
||||
BT_IO_OPT_IMTU, mtu,
|
||||
BT_IO_OPT_SEC_LEVEL, sec_level,
|
||||
BT_IO_OPT_INVALID);
|
||||
|
||||
if (err) {
|
||||
fprintf(stderr, "%s\n", err->message);
|
||||
g_error_free(err);
|
||||
return NULL;
|
||||
} else {
|
||||
return conn;
|
||||
}
|
||||
}
|
||||
|
||||
gatt_connection_t *gattlib_connect_async(const gchar *src, const gchar *dst,
|
||||
uint8_t dest_type, BtIOSecLevel sec_level, int psm, int mtu,
|
||||
gatt_connect_cb_t connect_cb)
|
||||
{
|
||||
io_connect_arg_t* io_connect_arg = malloc(sizeof(io_connect_arg_t));
|
||||
|
||||
return initialize_gattlib_connection(src, dst, dest_type, sec_level,
|
||||
psm, mtu, connect_cb, io_connect_arg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param src Local Adaptater interface
|
||||
* @param dst Remote Bluetooth address
|
||||
* @param dst_type Set LE address type (either BDADDR_LE_PUBLIC or BDADDR_LE_RANDOM)
|
||||
* @param sec_level Set security level (either BT_IO_SEC_LOW, BT_IO_SEC_MEDIUM, BT_IO_SEC_HIGH)
|
||||
* @param psm Specify the PSM for GATT/ATT over BR/EDR
|
||||
* @param mtu Specify the MTU size
|
||||
*/
|
||||
gatt_connection_t *gattlib_connect(const gchar *src, const gchar *dst,
|
||||
uint8_t dest_type, BtIOSecLevel sec_level, int psm, int mtu)
|
||||
{
|
||||
io_connect_arg_t io_connect_arg;
|
||||
|
||||
gatt_connection_t *conn = initialize_gattlib_connection(src, dst, dest_type, sec_level,
|
||||
psm, mtu, NULL, &io_connect_arg);
|
||||
|
||||
// Wait for the connection to be done
|
||||
while(io_connect_arg.connected == FALSE) {
|
||||
g_main_context_iteration(g_gattlib_thread.loop_context, FALSE);
|
||||
}
|
||||
|
||||
if (io_connect_arg.error) {
|
||||
fprintf(stderr, "gattlib_connect - error:%s\n", io_connect_arg.error->message);
|
||||
return NULL;
|
||||
} else {
|
||||
return conn;
|
||||
}
|
||||
}
|
||||
|
||||
int gattlib_disconnect(gatt_connection_t* connection) {
|
||||
// Stop the I/O Channel
|
||||
GIOStatus status = g_io_channel_shutdown(connection->io, FALSE, NULL);
|
||||
assert(status == G_IO_STATUS_NORMAL);
|
||||
g_io_channel_unref(connection->io);
|
||||
|
||||
g_attrib_unref(connection->attrib);
|
||||
|
||||
free(connection);
|
||||
|
||||
//TODO: Add a mutex around this code to avoid a race condition
|
||||
/* Decrease the reference counter of the loop */
|
||||
g_gattlib_thread.ref--;
|
||||
/* Check if we are the last one */
|
||||
if (g_gattlib_thread.ref == 0) {
|
||||
g_main_loop_quit(g_gattlib_thread.loop);
|
||||
g_main_loop_unref(g_gattlib_thread.loop);
|
||||
g_main_context_unref(g_gattlib_thread.loop_context);
|
||||
|
||||
// Detach the thread
|
||||
pthread_detach(g_gattlib_thread.thread);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
GSource* gattlib_watch_connection_full(GIOChannel* io, GIOCondition condition,
|
||||
GIOFunc func, gpointer user_data, GDestroyNotify notify)
|
||||
{
|
||||
// Create a main loop source
|
||||
GSource *source = g_io_create_watch (io, condition);
|
||||
assert(source != NULL);
|
||||
|
||||
g_source_set_callback (source, (GSourceFunc)func, user_data, notify);
|
||||
|
||||
// Attaches it to the main loop context
|
||||
guint id = g_source_attach(source, g_gattlib_thread.loop_context);
|
||||
g_source_unref (source);
|
||||
assert(id != 0);
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
GSource* gattlib_timeout_add_seconds(guint interval, GSourceFunc function, gpointer data) {
|
||||
GSource *source = g_timeout_source_new_seconds(interval);
|
||||
assert(source != NULL);
|
||||
|
||||
g_source_set_callback(source, function, data, NULL);
|
||||
|
||||
// Attaches it to the main loop context
|
||||
guint id = g_source_attach(source, g_gattlib_thread.loop_context);
|
||||
g_source_unref (source);
|
||||
assert(id != 0);
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
void gattlib_register_notification(gattlib_event_handler_t notification_handler) {
|
||||
g_notification_handler = notification_handler;
|
||||
}
|
||||
|
||||
void gattlib_register_indication(gattlib_event_handler_t indication_handler) {
|
||||
g_indication_handler = indication_handler;
|
||||
}
|
||||
|
||||
int gattlib_uuid_to_string(const bt_uuid_t *uuid, char *str, size_t n) {
|
||||
return bt_uuid_to_string(uuid, str, n);
|
||||
}
|
||||
|
||||
int gattlib_string_to_uuid(bt_uuid_t *uuid, const char *str) {
|
||||
return bt_string_to_uuid(uuid, str);
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
*
|
||||
* GattLib - GATT Library
|
||||
*
|
||||
* Copyright (C) 2016 Olivier Martin <olivier@labapart.org>
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "gattlib_internal.h"
|
||||
|
||||
#include "att.h"
|
||||
#include "gattrib.h"
|
||||
#include "gatt.h"
|
||||
|
||||
struct primary_all_cb_t {
|
||||
gattlib_primary_service_t* services;
|
||||
int services_count;
|
||||
int discovered;
|
||||
};
|
||||
|
||||
static void primary_all_cb(GSList *services, guint8 status, gpointer user_data) {
|
||||
struct primary_all_cb_t* data = user_data;
|
||||
GSList *l;
|
||||
int i;
|
||||
|
||||
if (status) {
|
||||
fprintf(stderr, "Discover all primary services failed: %s\n", att_ecode2str(status));
|
||||
goto done;
|
||||
}
|
||||
|
||||
// Allocate array
|
||||
data->services_count = g_slist_length(services);
|
||||
data->services = malloc(data->services_count * sizeof(gattlib_primary_service_t));
|
||||
|
||||
for (i = 0, l = services; l; l = l->next, i++) {
|
||||
struct gatt_primary *prim = l->data;
|
||||
|
||||
data->services[i].attr_handle_start = prim->range.start;
|
||||
data->services[i].attr_handle_end = prim->range.end;
|
||||
bt_string_to_uuid(&data->services[i].uuid, prim->uuid);
|
||||
|
||||
assert(i < data->services_count);
|
||||
}
|
||||
|
||||
done:
|
||||
data->discovered = TRUE;
|
||||
}
|
||||
|
||||
int gattlib_discover_primary(gatt_connection_t* connection, gattlib_primary_service_t** services, int* services_count) {
|
||||
struct primary_all_cb_t user_data;
|
||||
guint ret;
|
||||
|
||||
bzero(&user_data, sizeof(user_data));
|
||||
user_data.discovered = FALSE;
|
||||
|
||||
ret = gatt_discover_primary(connection->attrib, NULL, primary_all_cb, &user_data);
|
||||
if (ret == 0) {
|
||||
fprintf(stderr, "Fail to discover primary services.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Wait for completion
|
||||
while(user_data.discovered == FALSE) {
|
||||
g_main_context_iteration(g_gattlib_thread.loop_context, FALSE);
|
||||
}
|
||||
|
||||
*services = user_data.services;
|
||||
*services_count = user_data.services_count;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct characteristic_cb_t {
|
||||
gattlib_characteristic_t* characteristics;
|
||||
int characteristics_count;
|
||||
int discovered;
|
||||
};
|
||||
|
||||
static void characteristic_cb(GSList *characteristics, guint8 status, gpointer user_data) {
|
||||
struct characteristic_cb_t* data = user_data;
|
||||
GSList *l;
|
||||
int i;
|
||||
|
||||
if (status) {
|
||||
fprintf(stderr, "Discover all characteristics failed: %s\n", att_ecode2str(status));
|
||||
goto done;
|
||||
}
|
||||
|
||||
// Allocate array
|
||||
data->characteristics_count = g_slist_length(characteristics);
|
||||
data->characteristics = malloc(data->characteristics_count * sizeof(gattlib_characteristic_t));
|
||||
|
||||
for (i = 0, l = characteristics; l; l = l->next, i++) {
|
||||
struct gatt_char *chars = l->data;
|
||||
|
||||
data->characteristics[i].properties = chars->properties;
|
||||
data->characteristics[i].value_handle = chars->value_handle;
|
||||
bt_string_to_uuid(&data->characteristics[i].uuid, chars->uuid);
|
||||
|
||||
assert(i < data->characteristics_count);
|
||||
}
|
||||
|
||||
done:
|
||||
data->discovered = TRUE;
|
||||
}
|
||||
|
||||
int gattlib_discover_char(gatt_connection_t* connection, gattlib_characteristic_t** characteristics, int* characteristics_count) {
|
||||
struct characteristic_cb_t user_data;
|
||||
const int start = 0x0001;
|
||||
const int end = 0xffff;
|
||||
guint ret;
|
||||
|
||||
bzero(&user_data, sizeof(user_data));
|
||||
user_data.discovered = FALSE;
|
||||
|
||||
ret = gatt_discover_char(connection->attrib, start, end, NULL, characteristic_cb, &user_data);
|
||||
if (ret == 0) {
|
||||
fprintf(stderr, "Fail to discover characteristics.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Wait for completion
|
||||
while(user_data.discovered == FALSE) {
|
||||
g_main_context_iteration(g_gattlib_thread.loop_context, FALSE);
|
||||
}
|
||||
|
||||
*characteristics = user_data.characteristics;
|
||||
*characteristics_count = user_data.characteristics_count;
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
*
|
||||
* GattLib - GATT Library
|
||||
*
|
||||
* Copyright (C) 2016 Olivier Martin <olivier@labapart.org>
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "gattlib_internal.h"
|
||||
|
||||
#include "att.h"
|
||||
|
||||
struct gattlib_result_read_uuid_t {
|
||||
void* buffer;
|
||||
size_t buffer_max_len;
|
||||
size_t buffer_len;
|
||||
gatt_read_cb_t callback;
|
||||
int completed;
|
||||
};
|
||||
|
||||
static void gattlib_result_read_uuid_cb(guint8 status, const guint8 *pdu, guint16 len, gpointer user_data) {
|
||||
struct gattlib_result_read_uuid_t* gattlib_result = user_data;
|
||||
struct att_data_list *list;
|
||||
int i;
|
||||
|
||||
if (status == ATT_ECODE_ATTR_NOT_FOUND) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (status != 0) {
|
||||
fprintf(stderr, "Read characteristics by UUID failed: %s\n", att_ecode2str(status));
|
||||
goto done;
|
||||
}
|
||||
|
||||
list = dec_read_by_type_resp(pdu, len);
|
||||
if (list == NULL) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
for (i = 0; i < list->num; i++) {
|
||||
uint8_t *value = list->data[i];
|
||||
int j;
|
||||
|
||||
value += 2;
|
||||
|
||||
gattlib_result->buffer_len = list->len - 2;
|
||||
|
||||
if (gattlib_result->callback) {
|
||||
gattlib_result->callback(value, gattlib_result->buffer_len);
|
||||
} else {
|
||||
memcpy(gattlib_result->buffer, value, MIN(gattlib_result->buffer_len, gattlib_result->buffer_max_len));
|
||||
}
|
||||
}
|
||||
|
||||
att_data_list_free(list);
|
||||
|
||||
done:
|
||||
if (gattlib_result->callback) {
|
||||
free(gattlib_result);
|
||||
} else {
|
||||
gattlib_result->completed = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
int gattlib_read_char_by_uuid(gatt_connection_t* connection, bt_uuid_t* uuid,
|
||||
void* buffer, size_t buffer_len)
|
||||
{
|
||||
struct gattlib_result_read_uuid_t* gattlib_result;
|
||||
const int start = 0x0001;
|
||||
const int end = 0xffff;
|
||||
int len;
|
||||
|
||||
gattlib_result = malloc(sizeof(struct gattlib_result_read_uuid_t));
|
||||
if (gattlib_result == NULL) {
|
||||
return 0;
|
||||
}
|
||||
gattlib_result->buffer = buffer;
|
||||
gattlib_result->buffer_max_len = buffer_len;
|
||||
gattlib_result->buffer_len = 0;
|
||||
gattlib_result->callback = NULL;
|
||||
gattlib_result->completed = FALSE;
|
||||
|
||||
gatt_read_char_by_uuid(connection->attrib, start, end, uuid,
|
||||
gattlib_result_read_uuid_cb, gattlib_result);
|
||||
|
||||
// Wait for completion of the event
|
||||
while(gattlib_result->completed == FALSE) {
|
||||
g_main_context_iteration(g_gattlib_thread.loop_context, FALSE);
|
||||
}
|
||||
|
||||
len = gattlib_result->buffer_len;
|
||||
|
||||
free(gattlib_result);
|
||||
return len;
|
||||
}
|
||||
|
||||
int gattlib_read_char_by_uuid_async(gatt_connection_t* connection, bt_uuid_t* uuid,
|
||||
gatt_read_cb_t gatt_read_cb)
|
||||
{
|
||||
struct gattlib_result_read_uuid_t* gattlib_result;
|
||||
const int start = 0x0001;
|
||||
const int end = 0xffff;
|
||||
int i;
|
||||
|
||||
gattlib_result = malloc(sizeof(struct gattlib_result_read_uuid_t));
|
||||
if (gattlib_result == NULL) {
|
||||
return 0;
|
||||
}
|
||||
gattlib_result->buffer = NULL;
|
||||
gattlib_result->buffer_max_len = 0;
|
||||
gattlib_result->buffer_len = 0;
|
||||
gattlib_result->callback = gatt_read_cb;
|
||||
gattlib_result->completed = FALSE;
|
||||
|
||||
return gatt_read_char_by_uuid(connection->attrib, start, end, uuid,
|
||||
gattlib_result_read_uuid_cb, gattlib_result);
|
||||
}
|
||||
|
||||
void gattlib_write_result_cb(guint8 status, const guint8 *pdu, guint16 len, gpointer user_data) {
|
||||
int* write_completed = user_data;
|
||||
|
||||
*write_completed = TRUE;
|
||||
}
|
||||
|
||||
int gattlib_write_char_by_handle(gatt_connection_t* connection, uint16_t handle, void* buffer, size_t buffer_len) {
|
||||
int write_completed = FALSE;
|
||||
|
||||
guint ret = gatt_write_char(connection->attrib, handle, buffer, buffer_len,
|
||||
gattlib_write_result_cb, &write_completed);
|
||||
if (ret == 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Wait for completion of the event
|
||||
while(write_completed == FALSE) {
|
||||
g_main_context_iteration(g_gattlib_thread.loop_context, FALSE);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue