weliem / bluez_inc

A C library for Bluez (BLE) that hides all DBus communication. It doesn't get easier than this. This library can also be used in C++.
MIT License
84 stars 19 forks source link

Correct approach for having a peripheral disconnect itself from central device? #13

Closed lizziemac closed 11 months ago

lizziemac commented 11 months ago

Hi there Martijn!

Due to some client SDK issues for central devices, notably iOS, I need to have the peripheral disconnect itself. So far I've tried, in a specifically defined disconnect characteristic:

  1. Disconnecting the central device, and then removing said device. a. The main problem here is that in order to reconnect with the device, the user has to forget the bluetooth device, otherwise the reconnect fails. We have one specific flow where this is something we really don't want to have to do, and iOS doesn't enable applications to forget bluetooth devices. b. Another issue, probably because I shouldn't be disconnecting this way, is that other centrals that were previously added to the peripheral are also forgotten. (I'm seeing No cache for <central MAC address> when looking in /var/log/messages)
    binc_device_disconnect(device);
    binc_adapter_remove_device(default_adapter, device);
  2. Disconnecting the central device, and then advertising again. The problem here is that the disconnect call on its own is flaky, and that means that I need to occasionally call the disconnect multiple times. Then because its already advertising, I get an error saying the advertisement already exists.
    binc_device_disconnect(device);
    binc_adapter_start_advertising(default_adapter, advertisement);

Am I on the right track? Is there a different disconnect command or order of operations I should use? Also, is there a callback I can set for when the peripheral connection state changes? The central state change callback isn't called when just calling binc_device_disconnect.

I appreciate any input you may have, and I hope you're having a good day!

Liz

weliem commented 11 months ago

Not really sure if I fully understand what you are doing exactly. Please change some relevant code....

What I can say in general is that binc_device_disconnect is an async method. You have to wait for the disconnect to complete before doing anything else. You should certainly not remove that device before the disconnect is completed. If you do that you will not receive the callback because you erased the device from the internal 'administration'.....

lizziemac commented 11 months ago

Sure, here's the full context, although I think you may have already given me the hint I need by waiting for the disconnect to complete before deleting the device:

const char *on_local_char_write(const Application *application,
                                const char *address, const char *service_uuid,
                                const char *char_uuid, GByteArray *byte_array) {

  if ((g_str_equal(char_uuid, DISCONNECT_CHAR_UUID))) {
    LOGI("Disconnect requested: disconnecting from central device");
    Device *device =
        binc_adapter_get_device_by_address(default_adapter, address);
    LOGI("Device is %s", binc_device_get_path(device));
    if (device == NULL) {
      LOGE("Failed to get device by address");
      return BLUEZ_ERROR_FAILED;
    }
    binc_device_disconnect(device);
    binc_adapter_remove_device(default_adapter, device);

    // TODO: consider async disconnect to enable a happy return value?
    return NULL; // This command will always fail
  }
}
lizziemac commented 11 months ago

ok so I changed my logic to the following:

// main
// ....
    // Setup callback for device connection state changes
    binc_adapter_set_remote_central_cb(default_adapter,
                                       &on_central_state_changed);
// ... 
const char *on_local_char_write(const Application *application,
                                const char *address, const char *service_uuid,
                                const char *char_uuid, GByteArray *byte_array) {

  if ((g_str_equal(char_uuid, DISCONNECT_CHAR_UUID))) {
    LOGI("Disconnect requested: disconnecting from central device");
    Device *device =
        binc_adapter_get_device_by_address(default_adapter, address);
    LOGI("Device is %s", binc_device_get_path(device));
    if (device == NULL) {
      LOGE("Failed to get device by address");
      return BLUEZ_ERROR_FAILED;
    }
    binc_device_disconnect(device);

    return NULL; // This command will always fail
  }
}

void on_central_state_changed(Adapter *adapter, Device *device) {
  LOGI("device '%s' is now %s", binc_device_get_name(device),
       binc_device_get_connection_state_name(device));
  ConnectionState state = binc_device_get_connection_state(device);
  if (state == CONNECTED) {
    binc_adapter_stop_advertising(adapter, advertisement);
  } else if (state == DISCONNECTED) {
    // remove the device from the adapter
    binc_adapter_remove_device(adapter, device);
    // restart advertising
    binc_adapter_start_advertising(adapter, advertisement);
  }
}

But I'm never reaching the callback. Is this the right callback to use when disconnecting from the central device, as the peripheral? Interestingly though, I no longer need to forget the device, which is ideal! I just need to make sure I can resume advertising on disconnect.

lizziemac commented 11 months ago

Actually, it does hit it occasionally now - I just needed to reboot my device to see the benefit of not removing it except in the callback. thanks for the guidance!