/* * * GattLib - GATT Library * * Copyright (C) 2016-2019 Olivier Martin * * * 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 #include #include #include #include #include "gattlib_internal.h" #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; }; int gattlib_adapter_open(const char* adapter_name, void** adapter) { char object_path[20]; OrgBluezAdapter1 *adapter_proxy; GError *error = NULL; if (adapter == NULL) { return GATTLIB_INVALID_PARAMETER; } if (adapter_name) { snprintf(object_path, sizeof(object_path), "/org/bluez/%s", adapter_name); } else { strncpy(object_path, "/org/bluez/hci0", sizeof(object_path)); } adapter_proxy = org_bluez_adapter1_proxy_new_for_bus_sync( G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, "org.bluez", object_path, NULL, &error); if (adapter_proxy == NULL) { if (error) { fprintf(stderr, "Failed to get adapter %s: %s\n", object_path, error->message); g_error_free(error); } else { fprintf(stderr, "Failed to get adapter %s\n", object_path); } return GATTLIB_ERROR_DBUS; } // Ensure the adapter is powered on org_bluez_adapter1_set_powered(adapter_proxy, TRUE); *adapter = adapter_proxy; return GATTLIB_SUCCESS; } static gboolean stop_scan_func(gpointer data) { g_main_loop_quit(data); return FALSE; } /* * Internal structure to pass to Device Manager signal handlers */ struct discovered_device_arg { gattlib_discovered_device_t callback; GSList** discovered_devices_ptr; }; static void device_manager_on_device1_signal(const char* device1_path, struct discovered_device_arg *arg) { GError *error = NULL; OrgBluezDevice1* device1 = org_bluez_device1_proxy_new_for_bus_sync( G_BUS_TYPE_SYSTEM, G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, "org.bluez", device1_path, NULL, &error); if (error) { fprintf(stderr, "Failed to connection to new DBus Bluez Device: %s\n", error->message); g_error_free(error); } if (device1) { const gchar *address = org_bluez_device1_get_address(device1); // Check if the device is already part of the list GSList *item = g_slist_find_custom(*arg->discovered_devices_ptr, address, (GCompareFunc)g_ascii_strcasecmp); // First time this device is in the list if (item == NULL) { // Add the device to the list *arg->discovered_devices_ptr = g_slist_append(*arg->discovered_devices_ptr, g_strdup(address)); arg->callback( org_bluez_device1_get_address(device1), org_bluez_device1_get_name(device1)); g_object_unref(device1); } } } static void on_dbus_object_added(GDBusObjectManager *device_manager, GDBusObject *object, gpointer user_data) { const char* object_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(object)); GDBusInterface *interface = g_dbus_object_manager_get_interface(device_manager, object_path, "org.bluez.Device1"); if (!interface) { return; } // It is a 'org.bluez.Device1' device_manager_on_device1_signal(object_path, user_data); } static void on_interface_proxy_properties_changed (GDBusObjectManagerClient *device_manager, GDBusObjectProxy *object_proxy, GDBusProxy *interface_proxy, GVariant *changed_properties, const gchar *const *invalidated_properties, gpointer user_data) { // Check if the object is a 'org.bluez.Device1' if (strcmp(g_dbus_proxy_get_interface_name(interface_proxy), "org.bluez.Device1") != 0) { return; } // It is a 'org.bluez.Device1' device_manager_on_device1_signal(g_dbus_proxy_get_object_path(interface_proxy), user_data); } int gattlib_adapter_scan_enable(void* adapter, gattlib_discovered_device_t discovered_device_cb, int timeout) { GDBusObjectManager *device_manager; GError *error = NULL; int ret = GATTLIB_SUCCESS; int added_signal_id, changed_signal_id; GSList *discovered_devices = NULL; org_bluez_adapter1_call_start_discovery_sync((OrgBluezAdapter1*)adapter, NULL, &error); if (error) { fprintf(stderr, "Failed to start discovery: %s\n", error->message); g_error_free(error); return GATTLIB_ERROR_DBUS; } // // Get notification when objects are removed from the Bluez ObjectManager. // We should get notified when the connection is lost with the target to allow // us to advertise us again // 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"); } ret = GATTLIB_ERROR_DBUS; goto DISABLE_SCAN; } // Pass the user callback and the discovered device list pointer to the signal handlers struct discovered_device_arg discovered_device_arg = { .callback = discovered_device_cb, .discovered_devices_ptr = &discovered_devices }; added_signal_id = g_signal_connect(G_DBUS_OBJECT_MANAGER(device_manager), "object-added", G_CALLBACK (on_dbus_object_added), &discovered_device_arg); // List for object changes to see if there are still devices around changed_signal_id = g_signal_connect(G_DBUS_OBJECT_MANAGER(device_manager), "interface-proxy-properties-changed", G_CALLBACK(on_interface_proxy_properties_changed), &discovered_device_arg); // Run Glib loop for 'timeout' seconds GMainLoop *loop = g_main_loop_new(NULL, 0); g_timeout_add_seconds (timeout, stop_scan_func, loop); g_main_loop_run(loop); g_main_loop_unref(loop); g_signal_handler_disconnect(G_DBUS_OBJECT_MANAGER(device_manager), added_signal_id); g_signal_handler_disconnect(G_DBUS_OBJECT_MANAGER(device_manager), changed_signal_id); g_object_unref(device_manager); DISABLE_SCAN: // Stop BLE device discovery gattlib_adapter_scan_disable(adapter); // Free discovered device list g_slist_foreach(discovered_devices, (GFunc)g_free, NULL); g_slist_free(discovered_devices); return ret; } int gattlib_adapter_scan_disable(void* adapter) { GError *error = NULL; org_bluez_adapter1_call_stop_discovery_sync((OrgBluezAdapter1*)adapter, NULL, &error); // Ignore the error return GATTLIB_SUCCESS; } int gattlib_adapter_close(void* adapter) { g_object_unref(adapter); return GATTLIB_SUCCESS; } gboolean on_handle_device_property_change( OrgBluezGattCharacteristic1 *object, GVariant *arg_changed_properties, const gchar *const *arg_invalidated_properties, gpointer user_data) { gatt_connection_t* connection = user_data; gattlib_context_t* conn_context = connection->context; // Retrieve 'Value' from 'arg_changed_properties' if (g_variant_n_children (arg_changed_properties) > 0) { GVariantIter *iter; const gchar *key; GVariant *value; g_variant_get (arg_changed_properties, "a{sv}", &iter); while (g_variant_iter_loop (iter, "{&sv}", &key, &value)) { if (strcmp(key, "Connected") == 0) { if (!g_variant_get_boolean(value)) { // Disconnection case if (gattlib_has_valid_handler(&connection->disconnection)) { gattlib_call_disconnection_handler(&connection->disconnection); } } } else if (strcmp(key, "ServicesResolved") == 0) { if (g_variant_get_boolean(value)) { // Stop the timeout for connection g_source_remove(conn_context->connection_timeout); // Tell we are now connected g_main_loop_quit(conn_context->connection_loop); } } } } return TRUE; } /** * @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 char *src, const char *dst, unsigned long options) { GError *error = NULL; const char* adapter_name; char device_address_str[20 + 1]; char object_path[100]; int i; if (src) { adapter_name = src; } else { adapter_name = "hci0"; } // Transform string from 'DA:94:40:95:E0:87' to 'dev_DA_94_40_95_E0_87' strncpy(device_address_str, dst, sizeof(device_address_str)); for (i = 0; i < strlen(device_address_str); i++) { if (device_address_str[i] == ':') { device_address_str[i] = '_'; } } // Force a null-terminated character device_address_str[20] = '\0'; // Generate object path like: /org/bluez/hci0/dev_DA_94_40_95_E0_87 snprintf(object_path, sizeof(object_path), "/org/bluez/%s/dev_%s", adapter_name, device_address_str); gattlib_context_t* conn_context = calloc(sizeof(gattlib_context_t), 1); if (conn_context == NULL) { return NULL; } gatt_connection_t* connection = calloc(sizeof(gatt_connection_t), 1); if (connection == NULL) { goto FREE_CONN_CONTEXT; } else { connection->context = conn_context; } OrgBluezDevice1* device = org_bluez_device1_proxy_new_for_bus_sync( G_BUS_TYPE_SYSTEM, G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, "org.bluez", object_path, NULL, &error); if (device == NULL) { if (error) { fprintf(stderr, "Failed to connect to DBus Bluez Device: %s\n", error->message); g_error_free(error); } goto FREE_CONNECTION; } else { conn_context->device = device; conn_context->device_object_path = strdup(object_path); } // Register a handle for notification g_signal_connect(device, "g-properties-changed", G_CALLBACK (on_handle_device_property_change), connection); error = NULL; org_bluez_device1_call_connect_sync(device, NULL, &error); if (error) { if (strncmp(error->message, m_dbus_error_unknown_object, strlen(m_dbus_error_unknown_object)) == 0) { // You might have this error if the computer has not scanned or has not already had // pairing information about the targetted device. fprintf(stderr, "Device '%s' cannot be found\n", dst); } else { fprintf(stderr, "Device connected error (device:%s): %s\n", conn_context->device_object_path, error->message); } g_error_free(error); goto FREE_DEVICE; } // Wait for the property 'UUIDs' to be changed. We assume 'org.bluez.GattService1 // and 'org.bluez.GattCharacteristic1' to be advertised at that moment. conn_context->connection_loop = g_main_loop_new(NULL, 0); conn_context->connection_timeout = g_timeout_add_seconds(CONNECT_TIMEOUT, stop_scan_func, conn_context->connection_loop); g_main_loop_run(conn_context->connection_loop); g_main_loop_unref(conn_context->connection_loop); // Set the attribute to NULL even if not required conn_context->connection_loop = NULL; return connection; FREE_DEVICE: free(conn_context->device_object_path); g_object_unref(conn_context->device); FREE_CONNECTION: free(connection); FREE_CONN_CONTEXT: free(conn_context); return NULL; } gatt_connection_t *gattlib_connect_async(const char *src, const char *dst, unsigned long options, gatt_connect_cb_t connect_cb, void* data) { gatt_connection_t *connection; connection = gattlib_connect(src, dst, options); if ((connection != NULL) && (connect_cb != NULL)) { connect_cb(connection, data); } return connection; } int gattlib_disconnect(gatt_connection_t* connection) { gattlib_context_t* conn_context = connection->context; GError *error = NULL; org_bluez_device1_call_disconnect_sync(conn_context->device, NULL, &error); if (error) { fprintf(stderr, "Failed to disconnect DBus Bluez Device: %s\n", error->message); g_error_free(error); } free(conn_context->device_object_path); g_object_unref(conn_context->device); free(connection->context); free(connection); return GATTLIB_SUCCESS; } // 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) { gattlib_context_t* conn_context = connection->context; OrgBluezDevice1* device = conn_context->device; const gchar* const* service_str; GError *error = NULL; const gchar* const* service_strs = org_bluez_device1_get_gatt_services(device); if (service_strs == NULL) { if (services != NULL) { *services = NULL; } if (services_count != NULL) { *services_count = 0; } return GATTLIB_SUCCESS; } // Maximum number of primary services int count_max = 0, count = 0; for (service_str = service_strs; *service_str != NULL; service_str++) { count_max++; } gattlib_primary_service_t* primary_services = malloc(count_max * sizeof(gattlib_primary_service_t)); if (primary_services == NULL) { return GATTLIB_OUT_OF_MEMORY; } for (service_str = service_strs; *service_str != NULL; service_str++) { error = NULL; OrgBluezGattService1* service_proxy = org_bluez_gatt_service1_proxy_new_for_bus_sync( G_BUS_TYPE_SYSTEM, G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, "org.bluez", *service_str, NULL, &error); if (service_proxy == NULL) { if (error) { fprintf(stderr, "Failed to open service '%s': %s\n", *service_str, error->message); g_error_free(error); } else { fprintf(stderr, "Failed to open service '%s'.\n", *service_str); } continue; } if (org_bluez_gatt_service1_get_primary(service_proxy)) { primary_services[count].attr_handle_start = 0; primary_services[count].attr_handle_end = 0; gattlib_string_to_uuid( org_bluez_gatt_service1_get_uuid(service_proxy), MAX_LEN_UUID_STR + 1, &primary_services[count].uuid); count++; } g_object_unref(service_proxy); } if (services != NULL) { *services = primary_services; } if (services_count != NULL) { *services_count = count; } return GATTLIB_SUCCESS; } #else int gattlib_discover_primary(gatt_connection_t* connection, gattlib_primary_service_t** services, int* services_count) { gattlib_context_t* conn_context = connection->context; OrgBluezDevice1* device = conn_context->device; const gchar* const* service_str; GError *error = NULL; int ret = GATTLIB_SUCCESS; const gchar* const* service_strs = org_bluez_device1_get_uuids(device); if (service_strs == NULL) { if (services != NULL) { *services = NULL; } if (services_count != NULL) { *services_count = 0; } return GATTLIB_SUCCESS; } // Maximum number of primary services int count_max = 0, count = 0; for (service_str = service_strs; *service_str != NULL; service_str++) { count_max++; } gattlib_primary_service_t* primary_services = malloc(count_max * sizeof(gattlib_primary_service_t)); if (primary_services == NULL) { return GATTLIB_OUT_OF_MEMORY; } 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"); } ret = GATTLIB_ERROR_DBUS; goto ON_DEVICE_MANAGER_ERROR; } GList *objects = g_dbus_object_manager_get_objects(device_manager); GList *l; for (l = objects; l != NULL; l = l->next) { GDBusObject *object = l->data; const char* object_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(object)); GDBusInterface *interface = g_dbus_object_manager_get_interface(device_manager, object_path, "org.bluez.GattService1"); if (!interface) { continue; } error = NULL; OrgBluezGattService1* service_proxy = org_bluez_gatt_service1_proxy_new_for_bus_sync( G_BUS_TYPE_SYSTEM, G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, "org.bluez", object_path, NULL, &error); if (service_proxy == NULL) { if (error) { fprintf(stderr, "Failed to open service '%s': %s\n", object_path, error->message); g_error_free(error); } else { fprintf(stderr, "Failed to open service '%s'.\n", object_path); } continue; } // Ensure the service is attached to this device if (strcmp(conn_context->device_object_path, org_bluez_gatt_service1_get_device(service_proxy))) { g_object_unref(service_proxy); continue; } if (org_bluez_gatt_service1_get_primary(service_proxy)) { primary_services[count].attr_handle_start = 0; primary_services[count].attr_handle_end = 0; //Note: We assume the characteristics are always present after the services for (GList *m = l; m != NULL; m = m->next) { GDBusObject *characteristic_object = m->data; const char* characteristic_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(characteristic_object)); interface = g_dbus_object_manager_get_interface(device_manager, characteristic_path, "org.bluez.GattCharacteristic1"); if (!interface) { continue; } else if (strncmp(object_path, characteristic_path, strlen(object_path)) != 0) { continue; } else { int char_handle; // Object path is in the form '/org/bluez/hci0/dev_DE_79_A2_A1_E9_FA/service0024/char0029'. // We convert the last 4 hex characters into the handle sscanf(characteristic_path + strlen(characteristic_path) - 4, "%x", &char_handle); if (primary_services[count].attr_handle_start == 0) { primary_services[count].attr_handle_start = char_handle; } else { primary_services[count].attr_handle_start = MIN(primary_services[count].attr_handle_start, char_handle); } if (primary_services[count].attr_handle_end == 0) { primary_services[count].attr_handle_end = char_handle; } else { primary_services[count].attr_handle_end = MAX(primary_services[count].attr_handle_end, char_handle); } } } gattlib_string_to_uuid( org_bluez_gatt_service1_get_uuid(service_proxy), MAX_LEN_UUID_STR + 1, &primary_services[count].uuid); count++; } g_object_unref(service_proxy); } g_list_free_full(objects, g_object_unref); g_object_unref(device_manager); if (services != NULL) { *services = primary_services; } if (services_count != NULL) { *services_count = count; } ON_DEVICE_MANAGER_ERROR: if (ret != GATTLIB_SUCCESS) { free(primary_services); } return ret; } #endif // 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_char_range(gatt_connection_t* connection, int start, int end, gattlib_characteristic_t** characteristics, int* characteristics_count) { gattlib_context_t* conn_context = connection->context; OrgBluezDevice1* device = conn_context->device; GError *error = NULL; int handle; const gchar* const* service_strs = org_bluez_device1_get_gatt_services(device); const gchar* const* service_str; const gchar* const* characteristic_strs; const gchar* characteristic_str; if (service_strs == NULL) { return GATTLIB_INVALID_PARAMETER; } // Maximum number of primary services int count_max = 0, count = 0; for (service_str = service_strs; *service_str != NULL; service_str++) { error = NULL; OrgBluezGattService1* service_proxy = org_bluez_gatt_service1_proxy_new_for_bus_sync( G_BUS_TYPE_SYSTEM, G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, "org.bluez", *service_str, NULL, &error); if (service_proxy == NULL) { if (error) { fprintf(stderr, "Failed to open services '%s': %s\n", *service_str, error->message); g_error_free(error); } else { fprintf(stderr, "Failed to open services '%s'.\n", *service_str); } continue; } characteristic_strs = org_bluez_gatt_service1_get_characteristics(service_proxy); if (characteristic_strs == NULL) { g_object_unref(service_proxy); continue; } for (characteristic_str = *characteristic_strs; characteristic_str != NULL; characteristic_str++) { // Object path is in the form '/org/bluez/hci0/dev_DE_79_A2_A1_E9_FA/service0024/char0029'. // We convert the last 4 hex characters into the handle sscanf(characteristic_str + strlen(characteristic_str) - 4, "%x", &handle); // Check if handle is in range if ((handle < start) || (handle > end)) { continue; } count_max++; } g_object_unref(service_proxy); } gattlib_characteristic_t* characteristic_list = malloc(count_max * sizeof(gattlib_characteristic_t)); if (characteristic_list == NULL) { return GATTLIB_OUT_OF_MEMORY; } for (service_str = service_strs; *service_str != NULL; service_str++) { error = NULL; OrgBluezGattService1* service_proxy = org_bluez_gatt_service1_proxy_new_for_bus_sync( G_BUS_TYPE_SYSTEM, G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, "org.bluez", *service_str, NULL, &error); if (service_proxy == NULL) { if (error) { fprintf(stderr, "Failed to open service '%s': %s\n", *service_str, error->message); g_error_free(error); } else { fprintf(stderr, "Failed to open service '%s'.\n", *service_str); } continue; } characteristic_strs = org_bluez_gatt_service1_get_characteristics(service_proxy); if (characteristic_strs == NULL) { g_object_unref(service_proxy); continue; } for (characteristic_str = *characteristic_strs; characteristic_str != NULL; characteristic_str++) { // Object path is in the form '/org/bluez/hci0/dev_DE_79_A2_A1_E9_FA/service0024/char0029'. // We convert the last 4 hex characters into the handle sscanf(characteristic_str + strlen(characteristic_str) - 4, "%x", &handle); // Check if handle is in range if ((handle < start) || (handle > end)) { continue; } OrgBluezGattCharacteristic1 *characteristic_proxy = org_bluez_gatt_characteristic1_proxy_new_for_bus_sync( G_BUS_TYPE_SYSTEM, G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, "org.bluez", characteristic_str, NULL, &error); if (characteristic_proxy == NULL) { if (error) { fprintf(stderr, "Failed to open characteristic '%s': %s\n", characteristic_str, error->message); g_error_free(error); } else { fprintf(stderr, "Failed to open characteristic '%s'.\n", characteristic_str); } continue; } else { characteristic_list[count].handle = 0; characteristic_list[count].value_handle = 0; const gchar *const * flags = org_bluez_gatt_characteristic1_get_flags(characteristic_proxy); for (; *flags != NULL; flags++) { if (strcmp(*flags,"broadcast") == 0) { characteristic_list[count].properties |= GATTLIB_CHARACTERISTIC_BROADCAST; } else if (strcmp(*flags,"read") == 0) { characteristic_list[count].properties |= GATTLIB_CHARACTERISTIC_READ; } else if (strcmp(*flags,"write") == 0) { characteristic_list[count].properties |= GATTLIB_CHARACTERISTIC_WRITE; } else if (strcmp(*flags,"write-without-response") == 0) { characteristic_list[count].properties |= GATTLIB_CHARACTERISTIC_WRITE_WITHOUT_RESP; } else if (strcmp(*flags,"notify") == 0) { characteristic_list[count].properties |= GATTLIB_CHARACTERISTIC_NOTIFY; } else if (strcmp(*flags,"indicate") == 0) { characteristic_list[count].properties |= GATTLIB_CHARACTERISTIC_INDICATE; } } gattlib_string_to_uuid( org_bluez_gatt_characteristic1_get_uuid(characteristic_proxy), MAX_LEN_UUID_STR + 1, &characteristic_list[count].uuid); count++; } g_object_unref(characteristic_proxy); } g_object_unref(service_proxy); } *characteristics = characteristic_list; *characteristics_count = count; return GATTLIB_SUCCESS; } #else static void add_characteristics_from_service(GDBusObjectManager *device_manager, const char* service_object_path, int start, int end, gattlib_characteristic_t* characteristic_list, int* count) { GList *objects = g_dbus_object_manager_get_objects(device_manager); GError *error = NULL; GList *l; for (l = objects; l != NULL; l = l->next) { GDBusObject *object = l->data; const char* object_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(object)); GDBusInterface *interface = g_dbus_object_manager_get_interface(device_manager, object_path, "org.bluez.GattCharacteristic1"); if (!interface) { continue; } OrgBluezGattCharacteristic1* 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 == NULL) { if (error) { fprintf(stderr, "Failed to open characteristic '%s': %s\n", object_path, error->message); g_error_free(error); } else { fprintf(stderr, "Failed to open characteristic '%s'.\n", object_path); } continue; } if (strcmp(org_bluez_gatt_characteristic1_get_service(characteristic), service_object_path)) { g_object_unref(characteristic); continue; } else { int handle; // Object path is in the form '/org/bluez/hci0/dev_DE_79_A2_A1_E9_FA/service0024/char0029'. // We convert the last 4 hex characters into the handle sscanf(object_path + strlen(object_path) - 4, "%x", &handle); // Check if handle is in range if ((handle < start) || (handle > end)) { continue; } characteristic_list[*count].handle = handle; characteristic_list[*count].value_handle = handle; const gchar *const * flags = org_bluez_gatt_characteristic1_get_flags(characteristic); for (; *flags != NULL; flags++) { if (strcmp(*flags,"broadcast") == 0) { characteristic_list[*count].properties |= GATTLIB_CHARACTERISTIC_BROADCAST; } else if (strcmp(*flags,"read") == 0) { characteristic_list[*count].properties |= GATTLIB_CHARACTERISTIC_READ; } else if (strcmp(*flags,"write") == 0) { characteristic_list[*count].properties |= GATTLIB_CHARACTERISTIC_WRITE; } else if (strcmp(*flags,"write-without-response") == 0) { characteristic_list[*count].properties |= GATTLIB_CHARACTERISTIC_WRITE_WITHOUT_RESP; } else if (strcmp(*flags,"notify") == 0) { characteristic_list[*count].properties |= GATTLIB_CHARACTERISTIC_NOTIFY; } else if (strcmp(*flags,"indicate") == 0) { characteristic_list[*count].properties |= GATTLIB_CHARACTERISTIC_INDICATE; } } gattlib_string_to_uuid( org_bluez_gatt_characteristic1_get_uuid(characteristic), MAX_LEN_UUID_STR + 1, &characteristic_list[*count].uuid); *count = *count + 1; } g_object_unref(characteristic); } } int gattlib_discover_char_range(gatt_connection_t* connection, int start, int end, gattlib_characteristic_t** characteristics, int* characteristics_count) { gattlib_context_t* conn_context = connection->context; GError *error = NULL; GList *l; // Get list of services 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 GATTLIB_OUT_OF_MEMORY; } GList *objects = g_dbus_object_manager_get_objects(device_manager); // Count the maximum number of characteristic to allocate the array (we count all the characterstic for all devices) int count_max = 0, count = 0; for (l = objects; l != NULL; l = l->next) { GDBusObject *object = l->data; const char* object_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(object)); GDBusInterface *interface = g_dbus_object_manager_get_interface(device_manager, object_path, "org.bluez.GattCharacteristic1"); if (!interface) { continue; } count_max++; } gattlib_characteristic_t* characteristic_list = malloc(count_max * sizeof(gattlib_characteristic_t)); if (characteristic_list == NULL) { g_object_unref(device_manager); return GATTLIB_OUT_OF_MEMORY; } // List all services for this device for (l = objects; l != NULL; l = l->next) { GDBusObject *object = l->data; const char* object_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(object)); GDBusInterface *interface = g_dbus_object_manager_get_interface(device_manager, object_path, "org.bluez.GattService1"); if (!interface) { continue; } error = NULL; OrgBluezGattService1* service_proxy = org_bluez_gatt_service1_proxy_new_for_bus_sync( G_BUS_TYPE_SYSTEM, G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, "org.bluez", object_path, NULL, &error); if (service_proxy == NULL) { if (error) { fprintf(stderr, "Failed to open service '%s': %s\n", object_path, error->message); g_error_free(error); } else { fprintf(stderr, "Failed to open service '%s'.\n", object_path); } continue; } // Ensure the service is attached to this device const char* service_object_path = org_bluez_gatt_service1_get_device(service_proxy); if (strcmp(conn_context->device_object_path, service_object_path)) { g_object_unref(service_proxy); continue; } // Add all characteristics attached to this service add_characteristics_from_service(device_manager, object_path, start, end, characteristic_list, &count); g_object_unref(service_proxy); } g_list_free_full(objects, g_object_unref); g_object_unref(device_manager); *characteristics = characteristic_list; *characteristics_count = count; return GATTLIB_SUCCESS; } #endif int gattlib_discover_char(gatt_connection_t* connection, gattlib_characteristic_t** characteristics, int* characteristics_count) { 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; } int gattlib_discover_desc(gatt_connection_t* connection, gattlib_descriptor_t** descriptors, int* descriptor_count) { 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, GVariant *arg_changed_properties, const gchar *const *arg_invalidated_properties, gpointer user_data) { static guint8 percentage; gatt_connection_t* connection = user_data; if (gattlib_has_valid_handler(&connection->notification)) { // Retrieve 'Value' from 'arg_changed_properties' if (g_variant_n_children (arg_changed_properties) > 0) { GVariantIter *iter; const gchar *key; GVariant *value; g_variant_get (arg_changed_properties, "a{sv}", &iter); while (g_variant_iter_loop (iter, "{&sv}", &key, &value)) { if (strcmp(key, "Percentage") == 0) { //TODO: by declaring 'percentage' as a 'static' would mean we could have issue in case of multiple // GATT connection notifiying to Battery level percentage = g_variant_get_byte(value); gattlib_call_notification_handler(&connection->notification, &m_battery_level_uuid, (const uint8_t*)&percentage, sizeof(percentage)); break; } } } } return TRUE; } #endif static gboolean on_handle_characteristic_property_change( OrgBluezGattCharacteristic1 *object, GVariant *arg_changed_properties, const gchar *const *arg_invalidated_properties, gpointer user_data) { gatt_connection_t* connection = user_data; if (gattlib_has_valid_handler(&connection->notification)) { // Retrieve 'Value' from 'arg_changed_properties' if (g_variant_n_children (arg_changed_properties) > 0) { GVariantIter *iter; const gchar *key; GVariant *value; g_variant_get (arg_changed_properties, "a{sv}", &iter); while (g_variant_iter_loop (iter, "{&sv}", &key, &value)) { if (strcmp(key, "Value") == 0) { uuid_t uuid; size_t data_length; const uint8_t* data = g_variant_get_fixed_array(value, &data_length, sizeof(guchar)); gattlib_string_to_uuid( org_bluez_gatt_characteristic1_get_uuid(object), MAX_LEN_UUID_STR + 1, &uuid); gattlib_call_notification_handler(&connection->notification, &uuid, data, data_length); break; } } } } return TRUE; } int gattlib_notification_start(gatt_connection_t* connection, const uuid_t* uuid) { 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) { // Register a handle for notification g_signal_connect(dbus_characteristic.battery, "g-properties-changed", G_CALLBACK (on_handle_battery_level_property_change), connection); return GATTLIB_SUCCESS; } else { assert(dbus_characteristic.type == TYPE_GATT); } #endif // Register a handle for notification g_signal_connect(dbus_characteristic.gatt, "g-properties-changed", G_CALLBACK (on_handle_characteristic_property_change), connection); GError *error = NULL; org_bluez_gatt_characteristic1_call_start_notify_sync(dbus_characteristic.gatt, NULL, &error); if (error) { fprintf(stderr, "Failed to start DBus GATT notification: %s\n", error->message); g_error_free(error); return GATTLIB_ERROR_DBUS; } else { return GATTLIB_SUCCESS; } } int gattlib_notification_stop(gatt_connection_t* connection, const uuid_t* uuid) { 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) { g_signal_handlers_disconnect_by_func( dbus_characteristic.battery, G_CALLBACK (on_handle_battery_level_property_change), connection); return GATTLIB_SUCCESS; } else { assert(dbus_characteristic.type == TYPE_GATT); } #endif g_signal_handlers_disconnect_by_func( dbus_characteristic.gatt, G_CALLBACK (on_handle_characteristic_property_change), connection); GError *error = NULL; org_bluez_gatt_characteristic1_call_stop_notify_sync( dbus_characteristic.gatt, NULL, &error); if (error) { fprintf(stderr, "Failed to stop DBus GATT notification: %s\n", error->message); g_error_free(error); return GATTLIB_ERROR_DBUS; } else { return GATTLIB_SUCCESS; } }