mirror of https://github.com/labapart/gattlib
dbus: Move GATT characteristic code into 'gattlib_char.c'
parent
4b9252bad1
commit
ca92375558
|
@ -80,6 +80,7 @@ include_directories(. ${CMAKE_SOURCE_DIR}/common ${CMAKE_CURRENT_BINARY_DIR} ${G
|
|||
|
||||
set(gattlib_SRCS gattlib.c
|
||||
gattlib_adapter.c
|
||||
gattlib_char.c
|
||||
bluez5/lib/uuid.c
|
||||
${CMAKE_SOURCE_DIR}/common/gattlib_common.c
|
||||
${CMAKE_CURRENT_BINARY_DIR}/org-bluez-adaptater1.c
|
||||
|
|
425
dbus/gattlib.c
425
dbus/gattlib.c
|
@ -21,7 +21,6 @@
|
|||
*
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <glib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
@ -31,30 +30,8 @@
|
|||
|
||||
#define CONNECT_TIMEOUT 4
|
||||
|
||||
#define BLUEZ_GATT_WRITE_VALUE_TYPE_MASK (0x7)
|
||||
#define BLUEZ_GATT_WRITE_VALUE_TYPE_WRITE_WITH_RESPONSE (1 << 0)
|
||||
#define BLUEZ_GATT_WRITE_VALUE_TYPE_WRITE_WITHOUT_RESPONSE (1 << 1)
|
||||
#define BLUEZ_GATT_WRITE_VALUE_TYPE_RELIABLE_WRITE (1 << 2)
|
||||
|
||||
static const uuid_t m_battery_level_uuid = CREATE_UUID16(0x2A19);
|
||||
static const uuid_t m_ccc_uuid = CREATE_UUID16(0x2902);
|
||||
|
||||
static const char *m_dbus_error_unknown_object = "GDBus.Error:org.freedesktop.DBus.Error.UnknownObject";
|
||||
|
||||
struct dbus_characteristic {
|
||||
union {
|
||||
OrgBluezGattCharacteristic1 *gatt;
|
||||
#if BLUEZ_VERSION > BLUEZ_VERSIONS(5, 40)
|
||||
OrgBluezBattery1 *battery;
|
||||
#endif
|
||||
};
|
||||
enum {
|
||||
TYPE_NONE = 0,
|
||||
TYPE_GATT,
|
||||
TYPE_BATTERY_LEVEL
|
||||
} type;
|
||||
};
|
||||
|
||||
gboolean on_handle_device_property_change(
|
||||
OrgBluezGattCharacteristic1 *object,
|
||||
GVariant *arg_changed_properties,
|
||||
|
@ -810,209 +787,6 @@ int gattlib_discover_char(gatt_connection_t* connection, gattlib_characteristic_
|
|||
return gattlib_discover_char_range(connection, 0x00, 0xFF, characteristics, characteristics_count);
|
||||
}
|
||||
|
||||
static bool handle_dbus_gattcharacteristic_from_path(gattlib_context_t* conn_context, const uuid_t* uuid,
|
||||
struct dbus_characteristic *dbus_characteristic, const char* object_path, GError **error)
|
||||
{
|
||||
OrgBluezGattCharacteristic1 *characteristic = NULL;
|
||||
|
||||
*error = NULL;
|
||||
characteristic = org_bluez_gatt_characteristic1_proxy_new_for_bus_sync (
|
||||
G_BUS_TYPE_SYSTEM,
|
||||
G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
|
||||
"org.bluez",
|
||||
object_path,
|
||||
NULL,
|
||||
error);
|
||||
if (characteristic) {
|
||||
if (uuid != NULL) {
|
||||
uuid_t characteristic_uuid;
|
||||
const gchar *characteristic_uuid_str = org_bluez_gatt_characteristic1_get_uuid(characteristic);
|
||||
|
||||
gattlib_string_to_uuid(characteristic_uuid_str, strlen(characteristic_uuid_str) + 1, &characteristic_uuid);
|
||||
|
||||
if (gattlib_uuid_cmp(uuid, &characteristic_uuid) != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// We found the right characteristic, now we check if it's the right device.
|
||||
*error = NULL;
|
||||
OrgBluezGattService1* service = org_bluez_gatt_service1_proxy_new_for_bus_sync (
|
||||
G_BUS_TYPE_SYSTEM,
|
||||
G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
|
||||
"org.bluez",
|
||||
org_bluez_gatt_characteristic1_get_service(characteristic),
|
||||
NULL,
|
||||
error);
|
||||
|
||||
if (service) {
|
||||
const bool found = !strcmp(conn_context->device_object_path, org_bluez_gatt_service1_get_device(service));
|
||||
|
||||
g_object_unref(service);
|
||||
|
||||
if (found) {
|
||||
dbus_characteristic->gatt = characteristic;
|
||||
dbus_characteristic->type = TYPE_GATT;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
g_object_unref(characteristic);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#if BLUEZ_VERSION > BLUEZ_VERSIONS(5, 40)
|
||||
static bool handle_dbus_battery_from_uuid(gattlib_context_t* conn_context, const uuid_t* uuid,
|
||||
struct dbus_characteristic *dbus_characteristic, const char* object_path, GError **error)
|
||||
{
|
||||
OrgBluezBattery1 *battery = NULL;
|
||||
|
||||
*error = NULL;
|
||||
battery = org_bluez_battery1_proxy_new_for_bus_sync (
|
||||
G_BUS_TYPE_SYSTEM,
|
||||
G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
|
||||
"org.bluez",
|
||||
object_path,
|
||||
NULL,
|
||||
error);
|
||||
if (battery) {
|
||||
dbus_characteristic->battery = battery;
|
||||
dbus_characteristic->type = TYPE_BATTERY_LEVEL;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
static struct dbus_characteristic get_characteristic_from_uuid(gatt_connection_t* connection, const uuid_t* uuid) {
|
||||
gattlib_context_t* conn_context = connection->context;
|
||||
GError *error = NULL;
|
||||
bool is_battery_level_uuid = false;
|
||||
|
||||
struct dbus_characteristic dbus_characteristic = {
|
||||
.type = TYPE_NONE
|
||||
};
|
||||
|
||||
// Some GATT Characteristics are handled by D-BUS
|
||||
if (gattlib_uuid_cmp(uuid, &m_battery_level_uuid) == 0) {
|
||||
is_battery_level_uuid = true;
|
||||
} else if (gattlib_uuid_cmp(uuid, &m_ccc_uuid) == 0) {
|
||||
fprintf(stderr, "Error: Bluez v5.42+ does not expose Client Characteristic Configuration Descriptor through DBUS interface\n");
|
||||
return dbus_characteristic;
|
||||
}
|
||||
|
||||
GDBusObjectManager *device_manager = g_dbus_object_manager_client_new_for_bus_sync (
|
||||
G_BUS_TYPE_SYSTEM,
|
||||
G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
|
||||
"org.bluez",
|
||||
"/",
|
||||
NULL, NULL, NULL, NULL,
|
||||
&error);
|
||||
if (device_manager == NULL) {
|
||||
if (error) {
|
||||
fprintf(stderr, "Failed to get Bluez Device Manager: %s\n", error->message);
|
||||
g_error_free(error);
|
||||
} else {
|
||||
fprintf(stderr, "Failed to get Bluez Device Manager.\n");
|
||||
}
|
||||
return dbus_characteristic; // Return characteristic of type TYPE_NONE
|
||||
}
|
||||
|
||||
GList *objects = g_dbus_object_manager_get_objects(device_manager);
|
||||
GList *l;
|
||||
for (l = objects; l != NULL; l = l->next) {
|
||||
GDBusInterface *interface;
|
||||
bool found;
|
||||
GDBusObject *object = l->data;
|
||||
const char* object_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(object));
|
||||
|
||||
interface = g_dbus_object_manager_get_interface(device_manager, object_path, "org.bluez.GattCharacteristic1");
|
||||
if (interface) {
|
||||
found = handle_dbus_gattcharacteristic_from_path(conn_context, uuid, &dbus_characteristic, object_path, &error);
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found && is_battery_level_uuid) {
|
||||
#if BLUEZ_VERSION > BLUEZ_VERSIONS(5, 40)
|
||||
interface = g_dbus_object_manager_get_interface(device_manager, object_path, "org.bluez.Battery1");
|
||||
if (interface) {
|
||||
found = handle_dbus_battery_from_uuid(conn_context, uuid, &dbus_characteristic, object_path, &error);
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
#else
|
||||
fprintf(stderr, "You might use Bluez v5.48 with gattlib built for pre-v5.40\n");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
g_list_free_full(objects, g_object_unref);
|
||||
g_object_unref(device_manager);
|
||||
|
||||
return dbus_characteristic;
|
||||
}
|
||||
|
||||
static struct dbus_characteristic get_characteristic_from_handle(gatt_connection_t* connection, int handle) {
|
||||
gattlib_context_t* conn_context = connection->context;
|
||||
GError *error = NULL;
|
||||
int char_handle;
|
||||
|
||||
struct dbus_characteristic dbus_characteristic = {
|
||||
.type = TYPE_NONE
|
||||
};
|
||||
|
||||
GDBusObjectManager *device_manager = g_dbus_object_manager_client_new_for_bus_sync (
|
||||
G_BUS_TYPE_SYSTEM,
|
||||
G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
|
||||
"org.bluez",
|
||||
"/",
|
||||
NULL, NULL, NULL, NULL,
|
||||
&error);
|
||||
if (device_manager == NULL) {
|
||||
if (error) {
|
||||
fprintf(stderr, "Failed to get Bluez Device Manager: %s\n", error->message);
|
||||
g_error_free(error);
|
||||
} else {
|
||||
fprintf(stderr, "Failed to get Bluez Device Manager.\n");
|
||||
}
|
||||
return dbus_characteristic; // Return characteristic of type TYPE_NONE
|
||||
}
|
||||
|
||||
GList *objects = g_dbus_object_manager_get_objects(device_manager);
|
||||
for (GList *l = objects; l != NULL; l = l->next) {
|
||||
GDBusInterface *interface;
|
||||
bool found;
|
||||
GDBusObject *object = l->data;
|
||||
const char* object_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(object));
|
||||
|
||||
interface = g_dbus_object_manager_get_interface(device_manager, object_path, "org.bluez.GattCharacteristic1");
|
||||
if (interface) {
|
||||
// Object path is in the form '/org/bluez/hci0/dev_DE_79_A2_A1_E9_FA/service0024'.
|
||||
// We convert the last 4 hex characters into the handle
|
||||
sscanf(object_path + strlen(object_path) - 4, "%x", &char_handle);
|
||||
|
||||
if (char_handle != handle) {
|
||||
continue;
|
||||
}
|
||||
|
||||
found = handle_dbus_gattcharacteristic_from_path(conn_context, NULL, &dbus_characteristic, object_path, &error);
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g_list_free_full(objects, g_object_unref);
|
||||
g_object_unref(device_manager);
|
||||
|
||||
return dbus_characteristic;
|
||||
}
|
||||
|
||||
int gattlib_discover_desc_range(gatt_connection_t* connection, int start, int end, gattlib_descriptor_t** descriptors, int* descriptor_count) {
|
||||
return GATTLIB_NOT_SUPPORTED;
|
||||
}
|
||||
|
@ -1021,205 +795,6 @@ int gattlib_discover_desc(gatt_connection_t* connection, gattlib_descriptor_t**
|
|||
return GATTLIB_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
static int read_gatt_characteristic(struct dbus_characteristic *dbus_characteristic, void **buffer, size_t* buffer_len) {
|
||||
GVariant *out_value;
|
||||
GError *error = NULL;
|
||||
|
||||
#if BLUEZ_VERSION < BLUEZ_VERSIONS(5, 40)
|
||||
org_bluez_gatt_characteristic1_call_read_value_sync(
|
||||
dbus_characteristic->gatt, &out_value, NULL, &error);
|
||||
#else
|
||||
GVariantBuilder *options = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
|
||||
org_bluez_gatt_characteristic1_call_read_value_sync(
|
||||
dbus_characteristic->gatt, g_variant_builder_end(options), &out_value, NULL, &error);
|
||||
g_variant_builder_unref(options);
|
||||
#endif
|
||||
if (error != NULL) {
|
||||
fprintf(stderr, "Failed to read DBus GATT characteristic: %s\n", error->message);
|
||||
g_error_free(error);
|
||||
return GATTLIB_ERROR_DBUS;
|
||||
}
|
||||
|
||||
gsize n_elements = 0;
|
||||
gconstpointer const_buffer = g_variant_get_fixed_array(out_value, &n_elements, sizeof(guchar));
|
||||
if (const_buffer) {
|
||||
*buffer = malloc(n_elements);
|
||||
if (*buffer == NULL) {
|
||||
return GATTLIB_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
*buffer_len = n_elements;
|
||||
memcpy(*buffer, const_buffer, n_elements);
|
||||
} else {
|
||||
*buffer_len = 0;
|
||||
}
|
||||
|
||||
g_object_unref(dbus_characteristic->gatt);
|
||||
|
||||
#if BLUEZ_VERSION >= BLUEZ_VERSIONS(5, 40)
|
||||
//g_variant_unref(in_params); See: https://github.com/labapart/gattlib/issues/28#issuecomment-311486629
|
||||
#endif
|
||||
return GATTLIB_SUCCESS;
|
||||
}
|
||||
|
||||
#if BLUEZ_VERSION > BLUEZ_VERSIONS(5, 40)
|
||||
static int read_battery_level(struct dbus_characteristic *dbus_characteristic, void* buffer, size_t* buffer_len) {
|
||||
guchar percentage = org_bluez_battery1_get_percentage(dbus_characteristic->battery);
|
||||
|
||||
memcpy(buffer, &percentage, sizeof(uint8_t));
|
||||
*buffer_len = sizeof(uint8_t);
|
||||
|
||||
return GATTLIB_SUCCESS;
|
||||
}
|
||||
#endif
|
||||
|
||||
int gattlib_read_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid, void **buffer, size_t *buffer_len) {
|
||||
struct dbus_characteristic dbus_characteristic = get_characteristic_from_uuid(connection, uuid);
|
||||
if (dbus_characteristic.type == TYPE_NONE) {
|
||||
return GATTLIB_NOT_FOUND;
|
||||
}
|
||||
#if BLUEZ_VERSION > BLUEZ_VERSIONS(5, 40)
|
||||
else if (dbus_characteristic.type == TYPE_BATTERY_LEVEL) {
|
||||
return read_battery_level(&dbus_characteristic, buffer, buffer_len);
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
assert(dbus_characteristic.type == TYPE_GATT);
|
||||
|
||||
return read_gatt_characteristic(&dbus_characteristic, buffer, buffer_len);
|
||||
}
|
||||
}
|
||||
|
||||
int gattlib_read_char_by_uuid_async(gatt_connection_t* connection, uuid_t* uuid, gatt_read_cb_t gatt_read_cb) {
|
||||
struct dbus_characteristic dbus_characteristic = get_characteristic_from_uuid(connection, uuid);
|
||||
if (dbus_characteristic.type == TYPE_NONE) {
|
||||
return GATTLIB_NOT_FOUND;
|
||||
}
|
||||
#if BLUEZ_VERSION > BLUEZ_VERSIONS(5, 40)
|
||||
else if (dbus_characteristic.type == TYPE_BATTERY_LEVEL) {
|
||||
//TODO: Having 'percentage' as a 'static' is a limitation when we would support multiple connections
|
||||
static uint8_t percentage;
|
||||
|
||||
percentage = org_bluez_battery1_get_percentage(dbus_characteristic.battery);
|
||||
|
||||
gatt_read_cb((const void*)&percentage, sizeof(percentage));
|
||||
|
||||
return GATTLIB_SUCCESS;
|
||||
} else {
|
||||
assert(dbus_characteristic.type == TYPE_GATT);
|
||||
}
|
||||
#endif
|
||||
|
||||
GVariant *out_value;
|
||||
GError *error = NULL;
|
||||
|
||||
#if BLUEZ_VERSION < BLUEZ_VERSIONS(5, 40)
|
||||
org_bluez_gatt_characteristic1_call_read_value_sync(
|
||||
dbus_characteristic.gatt, &out_value, NULL, &error);
|
||||
#else
|
||||
GVariantBuilder *options = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
|
||||
org_bluez_gatt_characteristic1_call_read_value_sync(
|
||||
dbus_characteristic.gatt, g_variant_builder_end(options), &out_value, NULL, &error);
|
||||
g_variant_builder_unref(options);
|
||||
#endif
|
||||
if (error != NULL) {
|
||||
fprintf(stderr, "Failed to read DBus GATT characteristic: %s\n", error->message);
|
||||
g_error_free(error);
|
||||
return GATTLIB_ERROR_DBUS;
|
||||
}
|
||||
|
||||
gsize n_elements;
|
||||
gconstpointer const_buffer = g_variant_get_fixed_array(out_value, &n_elements, sizeof(guchar));
|
||||
if (const_buffer) {
|
||||
gatt_read_cb(const_buffer, n_elements);
|
||||
}
|
||||
|
||||
g_object_unref(dbus_characteristic.gatt);
|
||||
|
||||
#if BLUEZ_VERSION >= BLUEZ_VERSIONS(5, 40)
|
||||
//g_variant_unref(in_params); See: https://github.com/labapart/gattlib/issues/28#issuecomment-311486629
|
||||
#endif
|
||||
return GATTLIB_SUCCESS;
|
||||
}
|
||||
|
||||
static int write_char(struct dbus_characteristic *dbus_characteristic, const void* buffer, size_t buffer_len, uint32_t options)
|
||||
{
|
||||
GVariant *value = g_variant_new_from_data(G_VARIANT_TYPE ("ay"), buffer, buffer_len, TRUE, NULL, NULL);
|
||||
GError *error = NULL;
|
||||
|
||||
#if BLUEZ_VERSION < BLUEZ_VERSIONS(5, 40)
|
||||
org_bluez_gatt_characteristic1_call_write_value_sync(dbus_characteristic->gatt, value, NULL, &error);
|
||||
#else
|
||||
GVariantBuilder *variant_options = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
|
||||
|
||||
if ((options & BLUEZ_GATT_WRITE_VALUE_TYPE_MASK) == BLUEZ_GATT_WRITE_VALUE_TYPE_WRITE_WITHOUT_RESPONSE) {
|
||||
g_variant_builder_add(variant_options, "{sv}", "type", g_variant_new("s", "request"));
|
||||
}
|
||||
|
||||
org_bluez_gatt_characteristic1_call_write_value_sync(dbus_characteristic->gatt, value, g_variant_builder_end(variant_options), NULL, &error);
|
||||
g_variant_builder_unref(variant_options);
|
||||
#endif
|
||||
|
||||
if (error != NULL) {
|
||||
fprintf(stderr, "Failed to write DBus GATT characteristic: %s\n", error->message);
|
||||
g_error_free(error);
|
||||
return GATTLIB_ERROR_DBUS;
|
||||
}
|
||||
|
||||
g_object_unref(dbus_characteristic->gatt);
|
||||
#if BLUEZ_VERSION >= BLUEZ_VERSIONS(5, 40)
|
||||
//g_variant_unref(in_params); See: https://github.com/labapart/gattlib/issues/28#issuecomment-311486629
|
||||
#endif
|
||||
return GATTLIB_SUCCESS;
|
||||
}
|
||||
|
||||
int gattlib_write_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid, const void* buffer, size_t buffer_len) {
|
||||
struct dbus_characteristic dbus_characteristic = get_characteristic_from_uuid(connection, uuid);
|
||||
if (dbus_characteristic.type == TYPE_NONE) {
|
||||
return GATTLIB_NOT_FOUND;
|
||||
} else if (dbus_characteristic.type == TYPE_BATTERY_LEVEL) {
|
||||
return GATTLIB_NOT_SUPPORTED; // Battery level does not support write
|
||||
} else {
|
||||
assert(dbus_characteristic.type == TYPE_GATT);
|
||||
}
|
||||
|
||||
return write_char(&dbus_characteristic, buffer, buffer_len, BLUEZ_GATT_WRITE_VALUE_TYPE_WRITE_WITH_RESPONSE);
|
||||
}
|
||||
|
||||
int gattlib_write_char_by_handle(gatt_connection_t* connection, uint16_t handle, const void* buffer, size_t buffer_len) {
|
||||
struct dbus_characteristic dbus_characteristic = get_characteristic_from_handle(connection, handle);
|
||||
if (dbus_characteristic.type == TYPE_NONE) {
|
||||
return GATTLIB_NOT_FOUND;
|
||||
}
|
||||
|
||||
return write_char(&dbus_characteristic, buffer, buffer_len, BLUEZ_GATT_WRITE_VALUE_TYPE_WRITE_WITH_RESPONSE);
|
||||
}
|
||||
|
||||
int gattlib_write_without_response_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid, const void* buffer, size_t buffer_len)
|
||||
{
|
||||
struct dbus_characteristic dbus_characteristic = get_characteristic_from_uuid(connection, uuid);
|
||||
if (dbus_characteristic.type == TYPE_NONE) {
|
||||
return GATTLIB_NOT_FOUND;
|
||||
} else if (dbus_characteristic.type == TYPE_BATTERY_LEVEL) {
|
||||
return GATTLIB_NOT_SUPPORTED; // Battery level does not support write
|
||||
} else {
|
||||
assert(dbus_characteristic.type == TYPE_GATT);
|
||||
}
|
||||
|
||||
return write_char(&dbus_characteristic, buffer, buffer_len, BLUEZ_GATT_WRITE_VALUE_TYPE_WRITE_WITHOUT_RESPONSE);
|
||||
}
|
||||
|
||||
int gattlib_write_without_response_char_by_handle(gatt_connection_t* connection, uint16_t handle, const void* buffer, size_t buffer_len)
|
||||
{
|
||||
struct dbus_characteristic dbus_characteristic = get_characteristic_from_handle(connection, handle);
|
||||
if (dbus_characteristic.type == TYPE_NONE) {
|
||||
return GATTLIB_NOT_FOUND;
|
||||
}
|
||||
|
||||
return write_char(&dbus_characteristic, buffer, buffer_len, BLUEZ_GATT_WRITE_VALUE_TYPE_WRITE_WITHOUT_RESPONSE);
|
||||
}
|
||||
|
||||
|
||||
#if BLUEZ_VERSION > BLUEZ_VERSIONS(5, 40)
|
||||
gboolean on_handle_battery_level_property_change(
|
||||
OrgBluezBattery1 *object,
|
||||
|
|
|
@ -0,0 +1,437 @@
|
|||
/*
|
||||
*
|
||||
* GattLib - GATT Library
|
||||
*
|
||||
* Copyright (C) 2016-2019 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"
|
||||
|
||||
#define BLUEZ_GATT_WRITE_VALUE_TYPE_MASK (0x7)
|
||||
#define BLUEZ_GATT_WRITE_VALUE_TYPE_WRITE_WITH_RESPONSE (1 << 0)
|
||||
#define BLUEZ_GATT_WRITE_VALUE_TYPE_WRITE_WITHOUT_RESPONSE (1 << 1)
|
||||
#define BLUEZ_GATT_WRITE_VALUE_TYPE_RELIABLE_WRITE (1 << 2)
|
||||
|
||||
|
||||
const uuid_t m_battery_level_uuid = CREATE_UUID16(0x2A19);
|
||||
static const uuid_t m_ccc_uuid = CREATE_UUID16(0x2902);
|
||||
|
||||
|
||||
static bool handle_dbus_gattcharacteristic_from_path(gattlib_context_t* conn_context, const uuid_t* uuid,
|
||||
struct dbus_characteristic *dbus_characteristic, const char* object_path, GError **error)
|
||||
{
|
||||
OrgBluezGattCharacteristic1 *characteristic = NULL;
|
||||
|
||||
*error = NULL;
|
||||
characteristic = org_bluez_gatt_characteristic1_proxy_new_for_bus_sync (
|
||||
G_BUS_TYPE_SYSTEM,
|
||||
G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
|
||||
"org.bluez",
|
||||
object_path,
|
||||
NULL,
|
||||
error);
|
||||
if (characteristic) {
|
||||
if (uuid != NULL) {
|
||||
uuid_t characteristic_uuid;
|
||||
const gchar *characteristic_uuid_str = org_bluez_gatt_characteristic1_get_uuid(characteristic);
|
||||
|
||||
gattlib_string_to_uuid(characteristic_uuid_str, strlen(characteristic_uuid_str) + 1, &characteristic_uuid);
|
||||
|
||||
if (gattlib_uuid_cmp(uuid, &characteristic_uuid) != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// We found the right characteristic, now we check if it's the right device.
|
||||
*error = NULL;
|
||||
OrgBluezGattService1* service = org_bluez_gatt_service1_proxy_new_for_bus_sync (
|
||||
G_BUS_TYPE_SYSTEM,
|
||||
G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
|
||||
"org.bluez",
|
||||
org_bluez_gatt_characteristic1_get_service(characteristic),
|
||||
NULL,
|
||||
error);
|
||||
|
||||
if (service) {
|
||||
const bool found = !strcmp(conn_context->device_object_path, org_bluez_gatt_service1_get_device(service));
|
||||
|
||||
g_object_unref(service);
|
||||
|
||||
if (found) {
|
||||
dbus_characteristic->gatt = characteristic;
|
||||
dbus_characteristic->type = TYPE_GATT;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
g_object_unref(characteristic);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#if BLUEZ_VERSION > BLUEZ_VERSIONS(5, 40)
|
||||
static bool handle_dbus_battery_from_uuid(gattlib_context_t* conn_context, const uuid_t* uuid,
|
||||
struct dbus_characteristic *dbus_characteristic, const char* object_path, GError **error)
|
||||
{
|
||||
OrgBluezBattery1 *battery = NULL;
|
||||
|
||||
*error = NULL;
|
||||
battery = org_bluez_battery1_proxy_new_for_bus_sync (
|
||||
G_BUS_TYPE_SYSTEM,
|
||||
G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
|
||||
"org.bluez",
|
||||
object_path,
|
||||
NULL,
|
||||
error);
|
||||
if (battery) {
|
||||
dbus_characteristic->battery = battery;
|
||||
dbus_characteristic->type = TYPE_BATTERY_LEVEL;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct dbus_characteristic get_characteristic_from_uuid(gatt_connection_t* connection, const uuid_t* uuid) {
|
||||
gattlib_context_t* conn_context = connection->context;
|
||||
GError *error = NULL;
|
||||
bool is_battery_level_uuid = false;
|
||||
|
||||
struct dbus_characteristic dbus_characteristic = {
|
||||
.type = TYPE_NONE
|
||||
};
|
||||
|
||||
// Some GATT Characteristics are handled by D-BUS
|
||||
if (gattlib_uuid_cmp(uuid, &m_battery_level_uuid) == 0) {
|
||||
is_battery_level_uuid = true;
|
||||
} else if (gattlib_uuid_cmp(uuid, &m_ccc_uuid) == 0) {
|
||||
fprintf(stderr, "Error: Bluez v5.42+ does not expose Client Characteristic Configuration Descriptor through DBUS interface\n");
|
||||
return dbus_characteristic;
|
||||
}
|
||||
|
||||
GDBusObjectManager *device_manager = g_dbus_object_manager_client_new_for_bus_sync (
|
||||
G_BUS_TYPE_SYSTEM,
|
||||
G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
|
||||
"org.bluez",
|
||||
"/",
|
||||
NULL, NULL, NULL, NULL,
|
||||
&error);
|
||||
if (device_manager == NULL) {
|
||||
if (error) {
|
||||
fprintf(stderr, "Failed to get Bluez Device Manager: %s\n", error->message);
|
||||
g_error_free(error);
|
||||
} else {
|
||||
fprintf(stderr, "Failed to get Bluez Device Manager.\n");
|
||||
}
|
||||
return dbus_characteristic; // Return characteristic of type TYPE_NONE
|
||||
}
|
||||
|
||||
GList *objects = g_dbus_object_manager_get_objects(device_manager);
|
||||
GList *l;
|
||||
for (l = objects; l != NULL; l = l->next) {
|
||||
GDBusInterface *interface;
|
||||
bool found;
|
||||
GDBusObject *object = l->data;
|
||||
const char* object_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(object));
|
||||
|
||||
interface = g_dbus_object_manager_get_interface(device_manager, object_path, "org.bluez.GattCharacteristic1");
|
||||
if (interface) {
|
||||
found = handle_dbus_gattcharacteristic_from_path(conn_context, uuid, &dbus_characteristic, object_path, &error);
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found && is_battery_level_uuid) {
|
||||
#if BLUEZ_VERSION > BLUEZ_VERSIONS(5, 40)
|
||||
interface = g_dbus_object_manager_get_interface(device_manager, object_path, "org.bluez.Battery1");
|
||||
if (interface) {
|
||||
found = handle_dbus_battery_from_uuid(conn_context, uuid, &dbus_characteristic, object_path, &error);
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
#else
|
||||
fprintf(stderr, "You might use Bluez v5.48 with gattlib built for pre-v5.40\n");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
g_list_free_full(objects, g_object_unref);
|
||||
g_object_unref(device_manager);
|
||||
|
||||
return dbus_characteristic;
|
||||
}
|
||||
|
||||
static struct dbus_characteristic get_characteristic_from_handle(gatt_connection_t* connection, int handle) {
|
||||
gattlib_context_t* conn_context = connection->context;
|
||||
GError *error = NULL;
|
||||
int char_handle;
|
||||
|
||||
struct dbus_characteristic dbus_characteristic = {
|
||||
.type = TYPE_NONE
|
||||
};
|
||||
|
||||
GDBusObjectManager *device_manager = g_dbus_object_manager_client_new_for_bus_sync (
|
||||
G_BUS_TYPE_SYSTEM,
|
||||
G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
|
||||
"org.bluez",
|
||||
"/",
|
||||
NULL, NULL, NULL, NULL,
|
||||
&error);
|
||||
if (device_manager == NULL) {
|
||||
if (error) {
|
||||
fprintf(stderr, "Failed to get Bluez Device Manager: %s\n", error->message);
|
||||
g_error_free(error);
|
||||
} else {
|
||||
fprintf(stderr, "Failed to get Bluez Device Manager.\n");
|
||||
}
|
||||
return dbus_characteristic; // Return characteristic of type TYPE_NONE
|
||||
}
|
||||
|
||||
GList *objects = g_dbus_object_manager_get_objects(device_manager);
|
||||
for (GList *l = objects; l != NULL; l = l->next) {
|
||||
GDBusInterface *interface;
|
||||
bool found;
|
||||
GDBusObject *object = l->data;
|
||||
const char* object_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(object));
|
||||
|
||||
interface = g_dbus_object_manager_get_interface(device_manager, object_path, "org.bluez.GattCharacteristic1");
|
||||
if (interface) {
|
||||
// Object path is in the form '/org/bluez/hci0/dev_DE_79_A2_A1_E9_FA/service0024'.
|
||||
// We convert the last 4 hex characters into the handle
|
||||
sscanf(object_path + strlen(object_path) - 4, "%x", &char_handle);
|
||||
|
||||
if (char_handle != handle) {
|
||||
continue;
|
||||
}
|
||||
|
||||
found = handle_dbus_gattcharacteristic_from_path(conn_context, NULL, &dbus_characteristic, object_path, &error);
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g_list_free_full(objects, g_object_unref);
|
||||
g_object_unref(device_manager);
|
||||
|
||||
return dbus_characteristic;
|
||||
}
|
||||
|
||||
static int read_gatt_characteristic(struct dbus_characteristic *dbus_characteristic, void **buffer, size_t* buffer_len) {
|
||||
GVariant *out_value;
|
||||
GError *error = NULL;
|
||||
|
||||
#if BLUEZ_VERSION < BLUEZ_VERSIONS(5, 40)
|
||||
org_bluez_gatt_characteristic1_call_read_value_sync(
|
||||
dbus_characteristic->gatt, &out_value, NULL, &error);
|
||||
#else
|
||||
GVariantBuilder *options = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
|
||||
org_bluez_gatt_characteristic1_call_read_value_sync(
|
||||
dbus_characteristic->gatt, g_variant_builder_end(options), &out_value, NULL, &error);
|
||||
g_variant_builder_unref(options);
|
||||
#endif
|
||||
if (error != NULL) {
|
||||
fprintf(stderr, "Failed to read DBus GATT characteristic: %s\n", error->message);
|
||||
g_error_free(error);
|
||||
return GATTLIB_ERROR_DBUS;
|
||||
}
|
||||
|
||||
gsize n_elements = 0;
|
||||
gconstpointer const_buffer = g_variant_get_fixed_array(out_value, &n_elements, sizeof(guchar));
|
||||
if (const_buffer) {
|
||||
*buffer = malloc(n_elements);
|
||||
if (*buffer == NULL) {
|
||||
return GATTLIB_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
*buffer_len = n_elements;
|
||||
memcpy(*buffer, const_buffer, n_elements);
|
||||
} else {
|
||||
*buffer_len = 0;
|
||||
}
|
||||
|
||||
g_object_unref(dbus_characteristic->gatt);
|
||||
|
||||
#if BLUEZ_VERSION >= BLUEZ_VERSIONS(5, 40)
|
||||
//g_variant_unref(in_params); See: https://github.com/labapart/gattlib/issues/28#issuecomment-311486629
|
||||
#endif
|
||||
return GATTLIB_SUCCESS;
|
||||
}
|
||||
|
||||
#if BLUEZ_VERSION > BLUEZ_VERSIONS(5, 40)
|
||||
static int read_battery_level(struct dbus_characteristic *dbus_characteristic, void* buffer, size_t* buffer_len) {
|
||||
guchar percentage = org_bluez_battery1_get_percentage(dbus_characteristic->battery);
|
||||
|
||||
memcpy(buffer, &percentage, sizeof(uint8_t));
|
||||
*buffer_len = sizeof(uint8_t);
|
||||
|
||||
return GATTLIB_SUCCESS;
|
||||
}
|
||||
#endif
|
||||
|
||||
int gattlib_read_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid, void **buffer, size_t *buffer_len) {
|
||||
struct dbus_characteristic dbus_characteristic = get_characteristic_from_uuid(connection, uuid);
|
||||
if (dbus_characteristic.type == TYPE_NONE) {
|
||||
return GATTLIB_NOT_FOUND;
|
||||
}
|
||||
#if BLUEZ_VERSION > BLUEZ_VERSIONS(5, 40)
|
||||
else if (dbus_characteristic.type == TYPE_BATTERY_LEVEL) {
|
||||
return read_battery_level(&dbus_characteristic, buffer, buffer_len);
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
assert(dbus_characteristic.type == TYPE_GATT);
|
||||
|
||||
return read_gatt_characteristic(&dbus_characteristic, buffer, buffer_len);
|
||||
}
|
||||
}
|
||||
|
||||
int gattlib_read_char_by_uuid_async(gatt_connection_t* connection, uuid_t* uuid, gatt_read_cb_t gatt_read_cb) {
|
||||
struct dbus_characteristic dbus_characteristic = get_characteristic_from_uuid(connection, uuid);
|
||||
if (dbus_characteristic.type == TYPE_NONE) {
|
||||
return GATTLIB_NOT_FOUND;
|
||||
}
|
||||
#if BLUEZ_VERSION > BLUEZ_VERSIONS(5, 40)
|
||||
else if (dbus_characteristic.type == TYPE_BATTERY_LEVEL) {
|
||||
//TODO: Having 'percentage' as a 'static' is a limitation when we would support multiple connections
|
||||
static uint8_t percentage;
|
||||
|
||||
percentage = org_bluez_battery1_get_percentage(dbus_characteristic.battery);
|
||||
|
||||
gatt_read_cb((const void*)&percentage, sizeof(percentage));
|
||||
|
||||
return GATTLIB_SUCCESS;
|
||||
} else {
|
||||
assert(dbus_characteristic.type == TYPE_GATT);
|
||||
}
|
||||
#endif
|
||||
|
||||
GVariant *out_value;
|
||||
GError *error = NULL;
|
||||
|
||||
#if BLUEZ_VERSION < BLUEZ_VERSIONS(5, 40)
|
||||
org_bluez_gatt_characteristic1_call_read_value_sync(
|
||||
dbus_characteristic.gatt, &out_value, NULL, &error);
|
||||
#else
|
||||
GVariantBuilder *options = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
|
||||
org_bluez_gatt_characteristic1_call_read_value_sync(
|
||||
dbus_characteristic.gatt, g_variant_builder_end(options), &out_value, NULL, &error);
|
||||
g_variant_builder_unref(options);
|
||||
#endif
|
||||
if (error != NULL) {
|
||||
fprintf(stderr, "Failed to read DBus GATT characteristic: %s\n", error->message);
|
||||
g_error_free(error);
|
||||
return GATTLIB_ERROR_DBUS;
|
||||
}
|
||||
|
||||
gsize n_elements;
|
||||
gconstpointer const_buffer = g_variant_get_fixed_array(out_value, &n_elements, sizeof(guchar));
|
||||
if (const_buffer) {
|
||||
gatt_read_cb(const_buffer, n_elements);
|
||||
}
|
||||
|
||||
g_object_unref(dbus_characteristic.gatt);
|
||||
|
||||
#if BLUEZ_VERSION >= BLUEZ_VERSIONS(5, 40)
|
||||
//g_variant_unref(in_params); See: https://github.com/labapart/gattlib/issues/28#issuecomment-311486629
|
||||
#endif
|
||||
return GATTLIB_SUCCESS;
|
||||
}
|
||||
|
||||
static int write_char(struct dbus_characteristic *dbus_characteristic, const void* buffer, size_t buffer_len, uint32_t options)
|
||||
{
|
||||
GVariant *value = g_variant_new_from_data(G_VARIANT_TYPE ("ay"), buffer, buffer_len, TRUE, NULL, NULL);
|
||||
GError *error = NULL;
|
||||
|
||||
#if BLUEZ_VERSION < BLUEZ_VERSIONS(5, 40)
|
||||
org_bluez_gatt_characteristic1_call_write_value_sync(dbus_characteristic->gatt, value, NULL, &error);
|
||||
#else
|
||||
GVariantBuilder *variant_options = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
|
||||
|
||||
if ((options & BLUEZ_GATT_WRITE_VALUE_TYPE_MASK) == BLUEZ_GATT_WRITE_VALUE_TYPE_WRITE_WITHOUT_RESPONSE) {
|
||||
g_variant_builder_add(variant_options, "{sv}", "type", g_variant_new("s", "request"));
|
||||
}
|
||||
|
||||
org_bluez_gatt_characteristic1_call_write_value_sync(dbus_characteristic->gatt, value, g_variant_builder_end(variant_options), NULL, &error);
|
||||
g_variant_builder_unref(variant_options);
|
||||
#endif
|
||||
|
||||
if (error != NULL) {
|
||||
fprintf(stderr, "Failed to write DBus GATT characteristic: %s\n", error->message);
|
||||
g_error_free(error);
|
||||
return GATTLIB_ERROR_DBUS;
|
||||
}
|
||||
|
||||
g_object_unref(dbus_characteristic->gatt);
|
||||
#if BLUEZ_VERSION >= BLUEZ_VERSIONS(5, 40)
|
||||
//g_variant_unref(in_params); See: https://github.com/labapart/gattlib/issues/28#issuecomment-311486629
|
||||
#endif
|
||||
return GATTLIB_SUCCESS;
|
||||
}
|
||||
|
||||
int gattlib_write_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid, const void* buffer, size_t buffer_len) {
|
||||
struct dbus_characteristic dbus_characteristic = get_characteristic_from_uuid(connection, uuid);
|
||||
if (dbus_characteristic.type == TYPE_NONE) {
|
||||
return GATTLIB_NOT_FOUND;
|
||||
} else if (dbus_characteristic.type == TYPE_BATTERY_LEVEL) {
|
||||
return GATTLIB_NOT_SUPPORTED; // Battery level does not support write
|
||||
} else {
|
||||
assert(dbus_characteristic.type == TYPE_GATT);
|
||||
}
|
||||
|
||||
return write_char(&dbus_characteristic, buffer, buffer_len, BLUEZ_GATT_WRITE_VALUE_TYPE_WRITE_WITH_RESPONSE);
|
||||
}
|
||||
|
||||
int gattlib_write_char_by_handle(gatt_connection_t* connection, uint16_t handle, const void* buffer, size_t buffer_len) {
|
||||
struct dbus_characteristic dbus_characteristic = get_characteristic_from_handle(connection, handle);
|
||||
if (dbus_characteristic.type == TYPE_NONE) {
|
||||
return GATTLIB_NOT_FOUND;
|
||||
}
|
||||
|
||||
return write_char(&dbus_characteristic, buffer, buffer_len, BLUEZ_GATT_WRITE_VALUE_TYPE_WRITE_WITH_RESPONSE);
|
||||
}
|
||||
|
||||
int gattlib_write_without_response_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid, const void* buffer, size_t buffer_len)
|
||||
{
|
||||
struct dbus_characteristic dbus_characteristic = get_characteristic_from_uuid(connection, uuid);
|
||||
if (dbus_characteristic.type == TYPE_NONE) {
|
||||
return GATTLIB_NOT_FOUND;
|
||||
} else if (dbus_characteristic.type == TYPE_BATTERY_LEVEL) {
|
||||
return GATTLIB_NOT_SUPPORTED; // Battery level does not support write
|
||||
} else {
|
||||
assert(dbus_characteristic.type == TYPE_GATT);
|
||||
}
|
||||
|
||||
return write_char(&dbus_characteristic, buffer, buffer_len, BLUEZ_GATT_WRITE_VALUE_TYPE_WRITE_WITHOUT_RESPONSE);
|
||||
}
|
||||
|
||||
int gattlib_write_without_response_char_by_handle(gatt_connection_t* connection, uint16_t handle, const void* buffer, size_t buffer_len)
|
||||
{
|
||||
struct dbus_characteristic dbus_characteristic = get_characteristic_from_handle(connection, handle);
|
||||
if (dbus_characteristic.type == TYPE_NONE) {
|
||||
return GATTLIB_NOT_FOUND;
|
||||
}
|
||||
|
||||
return write_char(&dbus_characteristic, buffer, buffer_len, BLUEZ_GATT_WRITE_VALUE_TYPE_WRITE_WITHOUT_RESPONSE);
|
||||
}
|
|
@ -24,6 +24,8 @@
|
|||
#ifndef __GATTLIB_INTERNAL_H__
|
||||
#define __GATTLIB_INTERNAL_H__
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "gattlib_internal_defs.h"
|
||||
#include "gattlib.h"
|
||||
|
||||
|
@ -53,9 +55,27 @@ typedef struct {
|
|||
guint connection_timeout;
|
||||
} gattlib_context_t;
|
||||
|
||||
struct dbus_characteristic {
|
||||
union {
|
||||
OrgBluezGattCharacteristic1 *gatt;
|
||||
#if BLUEZ_VERSION > BLUEZ_VERSIONS(5, 40)
|
||||
OrgBluezBattery1 *battery;
|
||||
#endif
|
||||
};
|
||||
enum {
|
||||
TYPE_NONE = 0,
|
||||
TYPE_GATT,
|
||||
TYPE_BATTERY_LEVEL
|
||||
} type;
|
||||
};
|
||||
|
||||
extern const uuid_t m_battery_level_uuid;
|
||||
|
||||
gboolean stop_scan_func(gpointer data);
|
||||
|
||||
void get_device_path_from_mac_with_adapter(OrgBluezAdapter1* adapter, const char *mac_address, char *object_path, size_t object_path_len);
|
||||
void get_device_path_from_mac(const char *adapter_name, const char *mac_address, char *object_path, size_t object_path_len);
|
||||
|
||||
struct dbus_characteristic get_characteristic_from_uuid(gatt_connection_t* connection, const uuid_t* uuid);
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue